在java或者android中,使用Thread和Runnable就可以玩多線程了,這個成本比較低,也沒什么好說的,今天主要是針對多線程中主要的關鍵字wait,sleep,join和yield做個筆記,加強一下印象。
wait
wait方法一般都是和notity()或者notifyAll()成對出現的。當某個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時失去了對象的鎖功能,使得其他線程可以訪問該對象。用戶可以使用notify或者notifyAll或者指定睡眠時間來喚醒當前等待池中的線程。wait,notify和notifyAll方法都必須放在synchronized 代碼塊中,否則就會報java.lang.IllegalMonitorStateException。
下面的例子中,在主線程中會存在一個waitObject對象使用wait方法進行等待,而開啟子線程睡眠3秒之后,notifyAll該waitObject對象,使得主線程繼續執行:private static Object waitObject = new Object();
public static void main(String[] args) {
System.out.println("主線程開始運行");
WaitThread thread = new WaitThread();
thread.start();
long t1 = System.currentTimeMillis();
try{
synchronized(waitObject) {
System.out.println("主線程等待");
waitObject.wait();
System.out.println("主線程等待結束");
}
}catch(Exception e){
e.printStackTrace();
}
long t2 = System.currentTimeMillis();
System.out.println("最終時間為:" + (t2 - t1));
}
//定義等待線程
static class WaitThread extends Thread{
@Override
public void run() {
System.out.println("進入子線程run方法");
try{
sleep(3000);
synchronized(waitObject) {
waitObject.notifyAll();
System.out.println("子線程notifyAll結束");
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
程序運行的結果為:
可見wait和notity可用於等待機制的實現,當條件不滿足時進行等待,一旦條件滿足,則notity或者notifyAll喚醒等待線程繼續執行。經典的消費者-生產者模式可以使用wait和notity進行設計。
join
等待目標線程執行完成之后再繼續執行。說的比較含糊,還是來看看例子吧。下面有兩個工作子線程,都需要進行2s的耗時才能完成任務:public static void main(String[] args) throws Exception {
Thread t1 = new WorkThread("work1");
Thread t2 = new WorkThread("work2");
t1.start();
//t1.join();
t2.start();
//t2.join();
System.out.println("當前主線程結束");
}
//工作線程
static class WorkThread extends Thread{
public WorkThread(String name){
super(name);
}
@Override
public void run() {
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("the current thread is " + getName());
}
}
首先先注釋掉join方法,直接運行,可以看到的結果如下圖:
我們可以看到,主線程首先執行完成,而后兩個子線程分別執行完成,現在我們打開我們的t1.join()和t2.join()方法,得出的結果為:
程序執行的順序是 t1->t2->main thread,相當於三個程序的串聯執行,這也就是join的作用,一旦某個線程使用了join,那么它就先執行完畢,其他線程才有機會繼續執行。
yield
線程禮讓。使用yield方法的線程將由運行狀態轉變為就緒狀態,也就是讓出執行的狀態,讓其他線程得以優先執行,但是其他線程未必一定是有限執行的。簡單通俗一點,就是線程先等着,我會讓其他線程有優先的機會去運行。下面的例子可以簡單說明問題:有兩個單獨的子線程t1,t2分別獨自運行,t1中run遍歷到4時,會執行yield方法,那么我們的猜測就是此時t1將會變回就緒狀態,不再搶奪CPU資源,等到其他線程執行完畢后,再次從就緒狀態變為運行狀態,從而完成以后的任務。代碼如下:public static void main(String[] args) {
//線程t1運行到4時 會執行yield方法
Thread t1 = new YieldClass("線程1",4);
//線程t2將一直運行下去
Thread t2 = new YieldClass("線程2",-1);
t1.start();
t2.start();
System.out.println("主線程結束");
}
static class YieldClass extends Thread{
public int mIndex ;
public YieldClass(String name ,int index) {
super(name);
this.mIndex = index;
}
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
if(i == mIndex) {
yield();
}
}
}
}
我們來看運行的效果圖:
可以看到圖中,t1和t2先並行運行,可是當t1運行到4時,由於執行了yield方法,此時t1將會變為就緒狀態,t2線程會執行下去,t2線程執行完成之后,t1才會繼續執行,跟我們預想的方式是一樣的。
sleep
sleep方法是我們平常用得最多的,它是Thread的靜態函數,作用是使得調用的Thread進入休眠狀態。由於是Static修飾的方法,因此不能修改對象的鎖機制,所以當一個synchronized塊中調用了sleep方法,線程雖然休眠了,但是對象的鎖機制並沒有被釋放。其他線程將會無法訪問到這個對象。下面舉個例子:有兩個子線程,一個需要睡眠3s,另一個不需要睡眠,兩個子線程都使用了synchronized塊,我們來看看兩個線程結束之后所用的時間,代碼如下:public class TestSleep {
private static Object mLock = new Object();
public static void main(String[] args) {
Thread t1 = new SleepThread();
Thread t2 = new WordThread() ;
t1.start();
t2.start();
System.out.println("-----主線程執行完成-----"+ System.currentTimeMillis());
}
static class SleepThread extends Thread {
@Override
public void run() {
synchronized(mLock){
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----睡眠線程執行完成-----" + System.currentTimeMillis());
}
}
}
static class WordThread extends Thread{
@Override
public void run() {
synchronized (mLock) {
System.out.println("----工作線程執行完成-----" + System.currentTimeMillis());
}
}
}
}
結果為:
我們發現了睡眠線程和工作線程幾乎都是等待了3秒之后才結束的,這就表明了sleep引用了對象鎖,其他線程將無法訪問該對象了。我們去掉synchronized代碼塊,再來看一次結果:
我們發現睡眠線程和主線程幾乎同時完成工作,只有睡眠線程睡眠3秒之后才結束工作,由於沒有引用相同的對象,線程之間不影響各自的工作,此時就不存在同步的問題了。
好了,今天基本上就說到這里了,由於以前很少了解這些東西,以致很多關於多線程方面的東西都看得不是很懂,今天算是個入門吧。