多線程實戰(一)——多線程輪流調用

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

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

發佈了25 篇原創文章 · 獲贊 39 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章