師傅留了一個作業讓我們來熟悉多線程問題,原本對多線程一直處於理論階段,大二學操作系統的時候寫的也是一知半解,今天拿到這道題又好好的做了一遍。
題目:審覈系統有一批工單需要處理,現在啓動三個線程進行處理,要求線程1處理工單id mod 3 = 1的工單,線程2處理工單id mod 3 = 2的工單,線程3處理工單id mod 3 = 0的工單,直到工單全部處理完畢,假設工單有1000個,工單編號從1-1000,工單處理過程簡化爲打印工單id,要求工單必須按順序處理,即打印結果必須保證從1-1000從小到大遞增
1、請使用原始synchronized,wait(),notify(),notifyAll()等方式來實現。
2、使用JDK1.5併發包提供的Lock,Condition等類的相關方法來實現。
對於第一個問題,網上有很多相關的代碼,有重寫三個run方法的,甚至有創建1000個進程的,很多都不是特別的好。受到一篇博文的啓發@zyplus,我寫出了下面的代碼,優勢之處在於傳參的話只需要重寫一個run方法,代碼也相對優美一些。具體思想是這樣的,其實我們要做的知識對3個線程的循環調用,爲了控制三個線程的前後順序,需要定義兩個鎖,一個是prev,即前一個線程持有的對象鎖,第二個是self,用來使下一個線程進行等待和喚醒狀態轉換。
public class GongdanHandler extends Thread {
private String name;
private Object prev;
private Object self;
private static int id = 1;
private GongdanHandler(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
public void run() {
while (id <= 1000) {
synchronized (prev) {
synchronized (self) {
System.out.println(name + "處理工單" + id);
id++;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
GongdanHandler ta = new GongdanHandler("線程1", c, a);
GongdanHandler tb = new GongdanHandler("線程2", a, b);
GongdanHandler tc = new GongdanHandler("線程3", b, c);
ta.start();
tb.start();
tc.start();
}
}
對於第二個問題,就有點頭疼了,看了不少對於Lock和Condition的介紹和代碼,感覺還是有點懵逼,對於Condition,作爲新手的我看了這些方法
await(): 使當前線程在接到信號或被中斷之前處於等待狀態。
signal(): 喚醒一個等待線程。
signalAll(): 喚醒所有的等待線程。
這樣的話,Condition承擔的工作其實就是代替了被鎖鎖住的對象,使它的使用更加靈活且更加好理解一些了。
本來我還是想把代碼寫成和上一個代碼相同的思想的,這樣會大大的簡化代碼,但是過程中發現,在線程中需要使用到Condition對象,覺得傳的參數有點小多,就多重寫幾個run吧(在寫這段話的時候我把這兩份代碼又進行了一次比較,覺得還是第一份代碼寫得好,嘗試着把第二份代碼也像上一個那樣去寫,雖然好像傳的參數有點多,出了點小bug,公司沒大有人了,先把這篇先寫完吧)
package test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by bjhl on 16/8/1.
*/
public class GongdanHandler2 extends Thread{
static class NumberWrapper {
public static int value = 1;
}
public static void main(String[] args) {
//初始化可重入鎖
final Lock lock = new ReentrantLock();
final Condition reachOneCondition = lock.newCondition();
final Condition reachTwoCondition = lock.newCondition();
final Condition reachThreeCondition = lock.newCondition();
final NumberWrapper num = new NumberWrapper();
Thread t1 = new Thread(new Runnable() {
public void run() {
while (num.value <= 1000) {
try {
lock.lock();
System.out.println("t1等待");
if(num.value != 1)
reachOneCondition.await();
if(num.value > 1000) break;
if (num.value % 3 == 1) {
System.out.println("t1處理:" + num.value);
num.value++;
}
reachTwoCondition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
while (num.value <= 1000) {
try {
lock.lock();
System.out.println("t2等待");
reachTwoCondition.await();
if(num.value > 1000) break;
if (num.value % 3 == 2) {
System.out.println("t2處理:" + num.value);
num.value++;
}
reachThreeCondition.signal();
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
while (num.value <= 1000) {
try {
lock.lock();
System.out.println("t3等待");
reachThreeCondition.await();
if(num.value > 1000) break;
if (num.value % 3 == 0) {
System.out.println("t3處理:" + num.value);
num.value++;
}
reachOneCondition.signal();
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
});
t3.start();
t2.start();
t1.start();
}
}
確實不大好看呀,這個代碼也是受到一個博文的啓發,一開始我的運行結果是這樣的
t1等待
t1處理:1
t1等待
t2等待
t3等待
死鎖,爲什麼會發生這種情況,因爲在t1中執行reachTwoCondition.signal();的時候t2中的reachTwoCondition還沒進入等待狀態,我學習的那篇博文的博主教導我們把線程的順序倒過來start不就行了麼,一開始我覺得挺有道理的,也像他那樣做了,運行一下,也挺成功的,後來有運行了幾次,發現成功到1000是個概率事件,經常會在500多呀800多呀甚至30多發生死鎖。爲什麼,因爲循環一定的次數之後總會有概率發生之前這樣t1中執行reachTwoCondition.signal();的時候t2中的reachTwoCondition還沒進入等待狀態,t2t3同樣狀態的情況,這就很尷尬了,我目前的解決辦法是
<span style="white-space:pre"> </span>reachTwoCondition.await();
Thread.sleep(10);//每個線程中加了這麼一句
if(num.value > 1000) break;
即在等待被喚醒後先等上一小段時間,但這確實不是一個非常好的辦法,我再繼續想辦法