Applet是在浏覽器中運行的小程序,Java也是從Applet開始風靡世界的。通過編寫這個Applet,我們可以學習到如下知識:
1. Applet及JApplet中的主要接口
2. 圖像的裝載及MediaTracker的使用
3. 線程的使用及多個線程直接的通訊
4. Thread.join()方法的使用
5. volatile關鍵字的使用
首先看看運行效果:點擊運行
動畫的主要部分是一個Applet,從codebase中讀取一組圖片文件,然後每隔1秒輪換顯示一張。代碼如下:
import javax.swing.JApplet;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
public class Animate extends JApplet
{
//圖片數量
private static final int NUM_OF_PIC = 4;
int count;
Image pics[];
TimerThread timer;
public void init()
{
count = 1;
pics = new Image[NUM_OF_PIC];
MediaTracker tracker = new MediaTracker(this);
for(int i = 0; i<NUM_OF_PIC; i++)
{
//將圖片按照0,1,...,NUM_OF_PIC -1,放置在目錄中,格式為.jpg
pics[i] = getImage(getCodeBase(), new Integer(i).toString()+".jpg");
tracker.addImage(pics[i], 0);
}
tracker.checkAll(true);
}
public void start()
{
timer = new TimerThread(this, 1000);
timer.start();
}
public void stop()
{
timer.shouldRun = false;
try
{
timer.join();
//等待timer線程退出
}
catch (InterruptedException e){};
}
public void paint(Graphics g)
{
g.drawImage(pics[count++], 0, 0, null);
if(count == NUM_OF_PIC) count = 0;
}
}
動畫的控制由一個專門的線程TimerThread進行處理,
import java.awt.Component;
public class TimerThread extends Thread
{
Component comp;
int timediff;
// shouldRun聲明為volatile
volatile boolean shouldRun;
public TimerThread(Component comp, int timediff)
{
super("TimerThread(" + timediff + " millseconds");
this.comp = comp;
this.timediff = timediff;
shouldRun = true;
}
public void run()
{
while(shouldRun)
{
try
{
comp.repaint();
sleep(timediff);
}
catch (Exception e){}
}
}
}
MediaTracker的使用
在Applet中獲取一個圖像文件,可以調用Applet的getImage()方法。但是getImage方法會在調用後馬上返回,如果此時馬上使用getImage獲取的Image對象,而這時Image對象並沒有真正裝載或者裝載完成。所以,我們在使用圖像文件時,使用java.awt包中的MediaTracker跟蹤一個Image對象的裝載,可以保證所有圖片都加載完畢。使用MediaTracker需要如下三個步驟:
1、實例化一個MediaTracker,注意要將顯示圖片的Component對象作為參數傳入。
MediaTracker tracker = new MediaTracker(this);
2、將要裝載的Image對象加入MediaTracker
pics[i] = getImage(getCodeBase(),
new Integer(i).toString()+".jpg");
tracker.addImage(pics[i], 0);
3、調用MediaTracker的checkAll()方法,等待裝載過程的結束。
tracker.checkAll(true);
Thread.join()的使用
我們在Animate的stop方法中調用timer的join()方法,將timer線程連接(join)到當前線程,當前線程會一致會等待timer線程運行結束後,timer.join()方法才會返回。如果當前線程在等待timer返回的過程中,被其它線程中斷了,那麼當前線程會拋出InterruptedException。如果不使用Thread的join方法,那麼只能通過輪詢timer線程的狀態進行判斷了:
while (timer.isAlive())
{
try
{
Thread.sleep(50);
}
catch (InterruptedException e) {}
}
顯然這種辦法和使用join方法相比,會浪費cpu資源,同時也會浪費一些等待時間,因為當前線程每隔一段時間去查詢timer線程是否還存活,可能在timer線程已經結束了,但是當前線程還是要等待一段時間才能去監測它。
關於volatile
我們知道,在Java中設置變量值的操作,除了long和double類型的變量外都是原子操作,也就是說,對於變量值的簡單讀寫操作沒有必要進行同步。這在JVM 1.2之前,Java的內存模型實現總是從主存讀取變量,是不需要進行特別的注意的。而隨著JVM的成熟和優化,現在在多線程環境下volatile關鍵字的使用變得非常重要。在當前的Java內存模型下,線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致。要解決這個問題,只需要像在本程序中的這樣,把該變量聲明為volatile(不穩定的)即可,這就指示JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。一般說來,多任務環境下各任務間共享的標志都應該加volatile修飾。