[轉]Java 如何同步順序執行多個執行緒
轉載:http://hi.baidu.com/tianyadoudou/item/4deeb71dd6fbe7e55f53b18c
也許有人會問 “既然用了多執行緒,為什麼還要同步?還要順序執行呢?”。這個看似腦殘的問題其實並非我們想象的那麼簡單。
假設(這裡只是一個假設,類似下面的情形有很多,這裡不一一闡述)當你執行定時任務的時候,你需要執行ScheduledExecutorService的一個scheduleAtFixedRate方法的時候,那麼你需要給這個方法傳入一個執行緒A的例項。如果這個執行緒A是一個大的業務,這個大業務裡邊分多個步驟。假設第一個步驟需要用到多執行緒,而且業務需求是必須執行完第一步才能執行下面的操作,那麼慘了。因為多執行緒不等第一步執行完畢就有可能執行第二步的操作,那我們該怎麼辦?
我們可以把每個步驟看做是一個執行緒,執行完一個,再執行下一個,那麼該如何讓這些執行緒同步並且順序地執行呢?
(在網上找了好多資料,有說用join的,但是我沒弄出來。於是就自己硬搞了,希望有高手能用join解決一下,謝過!)
第一步是讓執行緒同步,第二部是讓執行緒有順序。
同步:我們可以用synchronized來解決。
java執行緒同步原理: java會為每個object物件分配一個monitor,當某個物件的同步方法(synchronized methods )被多個執行緒呼叫時,該物件的monitor將負責處理這些訪問的併發獨佔要求。
當一個執行緒呼叫一個物件的同步方法時,JVM會檢查該物件的monitor。如果monitor沒有被佔用,那麼這個執行緒就得到了monitor的佔有權,可以繼續執行該物件的同步方法;如果monitor被其他執行緒所佔用,那麼該執行緒將被掛起,直到monitor被釋放。
當執行緒退出同步方法呼叫時,該執行緒會釋放monitor,這將允許其他等待的執行緒獲得monitor以使對同步方法的呼叫執行下去。就像下面這樣:
public void test() {
synchronized (this) {
//做一些事
//這裡只會有一個執行緒來呼叫該方法,因為只有一個this物件作為資源分配給該執行緒
}
}
順序:我們可以用List來解決,因為它是有序的。我們只需要將要執行的執行緒放入到List中不就好解決了嗎
上程式碼:
/**
* 讓多個執行緒同步順序執行的執行緒管理器
* @author bianrx
* @date 2012.1.18
* SynchronizedRunMultiThread
*/
public class SyncManager {
/**
* 待執行的執行緒集合,注意這裡必須是Runnable介面型別,下面會解釋
*/
private List<Runnable> runnableList;
public SyncManager(){}
public SyncManager(List<Runnable> runnableList) {
this.runnableList = runnableList;
}
public void setRunnable(List<Runnable> runnableList) {
this.runnableList = runnableList;
}
public void run() {
//遍歷代執行的執行緒集合
for(Runnable runnable: runnableList) {
runThread(runnable);
}
}
/**
* 按順序同步執行多個執行緒
* @author bianrx
* @date 2012.1.18
* @param runnable
*/
private void runThread(Runnable runnable) {
synchronized (this) {
runnable.run();//這裡需要注意的是:必須呼叫run方法,因為如果你呼叫了start方法,執行緒只會向JVM請求資源,但是未必就執行其中的run。
//這個方法是同步的,所以當前只有一個執行緒佔用了this物件。
}
}
}
測試程式碼:有兩個要執行的執行緒
public class Thread1 extends Thread {
@Override
public void run() {
System.out.println("執行執行緒1");
}
}
public class Thread2 extends Thread {
@Override
public void run() {
System.out.println("執行執行緒2");
}
}
//主函式測試
public class MainTest {
/**
* @param args
* @throws ParseException
* @throws InterruptedException
*/
public static void main(String[] args) throws ParseException, InterruptedException {
List<Runnable> runnableList = new ArrayList<Runnable>();
runnableList.add(new Thread1());
runnableList.add(new Thread2());
while(true) {
Thread.sleep(1000);
new SyncManager(runnableList).run();
System.out.println("---------------");
}
}
在這裡補充一下:我做錯了。情況是這樣的,我要定時執行一個任務,而我查到的java定時任務的api是這樣的:
executorService.scheduleAtFixedRate(srtm, initialDelay, period, unit);
第一個引數是一個runnable介面。這個任務分兩個步驟,我為了方便維護程式,就分別寫到了兩個執行緒裡,實際上算不上併發。但是我的第一步裡確實是要有併發的(多執行緒),我上面寫的方法只能保證在主執行緒裡不巢狀子執行緒的情況下才好用,但是如果有巢狀該怎麼辦呢?這些是我遇到的情況,大家不必關注。也就是說想做到在主執行緒中等待多執行緒都執行完畢了再執行後續程式碼該怎麼辦呢?
答案是用join。join方法大家可以查下api,它的意思是等待當前執行緒執行完後執行完畢才執行其他執行緒。也就是說如果一個類中有這樣一個程式碼段:
thread1.start();
thread2.start();
thread1.join();
thread2.join();
do something 1;
do something 2;
那麼這段程式碼會等待兩個執行緒執行完畢後再執行 do something 1 和 do something 2,注意:必須先啟動所有執行緒,再join。如果啟動一個就join一個,結果是什麼?對,那就會是等待thread1執行完再執行thread2,再執行後續程式碼。
貼上自己測試程式碼:
package com.yonyou.mulitthread;
/**
* Created by lvyufan on 14-3-8.
*/
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread 1....");
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread 2....");
}
};
// 下面表示,t1執行緒執行完之後,才能執行t2執行緒,t2,執行緒執行完之後才能執行main執行緒
// t1.start();
// t1.join();
// t2.start();
// t2.join();
// t1,t2都start之後再分別join,表示t1,t2兩個執行緒都執行完之後,主執行緒main中得列印
// 語句才能執行。
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main thread run end ...");
}
}