1. 程式人生 > >多執行緒實戰(一)——多執行緒輪流呼叫

多執行緒實戰(一)——多執行緒輪流呼叫

師傅留了一個作業讓我們來熟悉多執行緒問題,原本對多執行緒一直處於理論階段,大二學作業系統的時候寫的也是一知半解,今天拿到這道題又好好的做了一遍。

題目:稽核系統有一批工單需要處理,現在啟動三個執行緒進行處理,要求執行緒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;
即在等待被喚醒後先等上一小段時間,但這確實不是一個非常好的辦法,我再繼續想辦法