Java多线程通信 — Condition with ReentrantLock

Condition(条件),通常用于与 Lock(如:上篇学习过的 ReentrantLock)结合使用,用于线程间的通信,让线程阻塞(await())和唤醒(signal()),作用类似于Object 的 wait(),notify() .

Condition 与 Lock 绑定,一个 Lock 可以创建无数个 Condition。

以下用一个“吃回旋寿司”的例子介绍一下(DEMO1):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public class Test {

    /**
     * 回旋寿司机器
     */
    static class SushiMachine {
	    // 非公平
        Lock mLock = new ReentrantLock();
        Condition mEmptyCondition = mLock.newCondition();
        Condition mFullCondition = mLock.newCondition();

        // 单位:碟
        final int MAX_DISH = 50;

        final int MIX_DIASH = 0;

        int mCurrentDish = 10;

        // 制作寿司
        public void produce(int dish) {
            mLock.lock();
            try {
                while (mCurrentDish >= MAX_DISH) {
                    mFullCondition.await();
                }
                if (mCurrentDish + dish - MAX_DISH > 0) {
                    int offset = MAX_DISH - mCurrentDish;
                    mCurrentDish += offset;
                    System.out.println(" 制作寿司 ---  想放 " + dish + " 碟,放不下这么多,只能放 " + offset + " 碟,放了后还剩 " + mCurrentDish + " 碟 ");
                } else {
                    mCurrentDish += dish;
                    System.out.println(" 制作寿司 ---  想放 " + dish + " 碟,放了 " + dish + " 碟,放了后还剩 " + mCurrentDish + " 碟 ");
                }
                mEmptyCondition.signal();
            } catch (InterruptedException e) {

            } finally {
                mLock.unlock();
            }
        }

        // 吃寿司
        public void consume(int dish) {
            mLock.lock();
            try {
                while (mCurrentDish <= MIX_DIASH) {
                    mEmptyCondition.await();
                }
                if (mCurrentDish - dish < MIX_DIASH) {
                    int mark = mCurrentDish;
                    mCurrentDish = MIX_DIASH;
                    System.out.println(" 吃寿司 --- 客人吃了 " + mark + " 碟,没得再吃了,现在还剩 " + mCurrentDish + " 碟 ");
                } else {
                    mCurrentDish = mCurrentDish - dish;
                    System.out.println(" 吃寿司 --- 客人吃了 " + dish + " 碟,现在还剩 " + mCurrentDish + " 碟 ");
                }
                mFullCondition.signal();
            } catch (InterruptedException e) {

            } finally {
                mLock.unlock();
            }
        }
    }

    /**
     * 小白兔回旋寿司店
     */
    static class SushiStore {

        SushiMachine mSushiMachine = new SushiMachine();

        void produce(final int dish) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mSushiMachine.produce(dish);
                }
            }).start();
        }

        void consume(final int dish) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mSushiMachine.consume(dish);
                }
            }).start();
        }
    }

    public static void main(String[] var0) {
        // 测试
        Test.SushiStore sushiStore = new Test.SushiStore();
        sushiStore.consume(2);
        sushiStore.produce(30);
        sushiStore.consume(40);
        sushiStore.consume(5);
        sushiStore.produce(10);
        sushiStore.produce(50);
        sushiStore.consume(30);
    }
}

DEMO1 的某次运行结果输出:

1
2
3
4
5
6
7
吃寿司 --- 客人吃了 2 碟,现在还剩 8 碟
制作寿司 ---  想放 30 碟,放了 30 碟,放了后还剩 38 碟
吃寿司 --- 客人吃了 38 碟,没得再吃了,现在还剩 0 碟
制作寿司 ---  想放 10 碟,放了 10 碟,放了后还剩 10 碟
制作寿司 ---  想放 50 碟,放不下这么多,只能放 40 碟,放了后还剩 50 碟
吃寿司 --- 客人吃了 5 碟,现在还剩 45 碟
吃寿司 --- 客人吃了 30 碟,现在还剩 15 碟

若使用公平锁(DEMO2):

1
2
// 公平锁
Lock mLock = new ReentrantLock(true);

DEMO2 的某次运行结果输出:

1
2
3
4
5
6
7
吃寿司 --- 客人吃了 2 碟,现在还剩 8 碟
制作寿司 ---  想放 30 碟,放了 30 碟,放了后还剩 38 碟
吃寿司 --- 客人吃了 38 碟,没得再吃了,现在还剩 0 碟
制作寿司 ---  想放 10 碟,放了 10 碟,放了后还剩 10 碟
吃寿司 --- 客人吃了 5 碟,现在还剩 5 碟
制作寿司 ---  想放 50 碟,放不下这么多,只能放 45 碟,放了后还剩 50 碟
吃寿司 --- 客人吃了 30 碟,现在还剩 20 碟

以上 demo 可以使用我们用得比较多的 Object 的 wait 和 notify 吗?
答案是:不可以。

DEMO3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
public class Test {

    /**
     * 回旋寿司机器
     */
    static class SushiMachine {
        Lock mLock = new ReentrantLock(true);
        Object mEmptyCondition = new Object();
        Object mFullCondition = new Object();

        // 单位:碟
        final int MAX_DISH = 50;

        final int MIX_DIASH = 0;

        int mCurrentDish = 10;

        // 制作寿司
        public void produce(int dish) {
            mLock.lock();
            try {
                while (mCurrentDish >= MAX_DISH) {
                    synchronized (mFullCondition){
                        mFullCondition.wait();
                    }
                }
                if (mCurrentDish + dish - MAX_DISH > 0) {
                    int offset = MAX_DISH - mCurrentDish;
                    mCurrentDish += offset;
                    System.out.println(" 制作寿司 ---  想放 " + dish + " 碟,放不下这么多,只能放 " + offset + " 碟,放了后还剩 " + mCurrentDish + " 碟 ");
                } else {
                    mCurrentDish += dish;
                    System.out.println(" 制作寿司 ---  想放 " + dish + " 碟,放了 " + dish + " 碟,放了后还剩 " + mCurrentDish + " 碟 ");
                }
                synchronized (mEmptyCondition){
                    mEmptyCondition.notify();
                }
            } catch (InterruptedException e) {

            } finally {
                mLock.unlock();
            }
        }

        // 吃寿司
        public void consume(int dish) {
            mLock.lock();
            try {
                while (mCurrentDish <= MIX_DIASH) {
                    synchronized (mEmptyCondition){
                        mEmptyCondition.wait();
                    }
                }
                if (mCurrentDish - dish < MIX_DIASH) {
                    int mark = mCurrentDish;
                    mCurrentDish = MIX_DIASH;
                    System.out.println(" 吃寿司 --- 客人吃了 " + mark + " 碟,没得再吃了,现在还剩 " + mCurrentDish + " 碟 ");
                } else {
                    mCurrentDish = mCurrentDish - dish;
                    System.out.println(" 吃寿司 --- 客人吃了 " + dish + " 碟,现在还剩 " + mCurrentDish + " 碟 ");
                }
                synchronized (mFullCondition){
                    mFullCondition.notify();
                }
            } catch (InterruptedException e) {

            } finally {
                mLock.unlock();
            }
        }
    }

    /**
     * 小白兔回旋寿司店
     */
    static class SushiStore {

        SushiMachine mSushiMachine = new SushiMachine();

        void produce(final int dish) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mSushiMachine.produce(dish);
                }
            }).start();
        }

        void consume(final int dish) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mSushiMachine.consume(dish);
                }
            }).start();
        }
    }

    public static void main(String[] var0) {
        // 测试
        Test.SushiStore sushiStore = new Test.SushiStore();
        sushiStore.consume(2);
        sushiStore.produce(30);
        sushiStore.consume(40);
        sushiStore.consume(5);
        sushiStore.produce(10);
        sushiStore.produce(50);
        sushiStore.consume(30);
    }
}

DEMO3 的某次运行结果输出:

1
2
3
吃寿司 --- 客人吃了 2 碟,现在还剩 8 碟
制作寿司 ---  想放 30 碟,放了 30 碟,放了后还剩 38 碟
吃寿司 --- 客人吃了 38 碟,没得再吃了,现在还剩 0 碟

程序并没有运行完,而是进入了死锁。

分析:
DEMO3 使用的是公平锁,所以会按顺序获取锁执行,输出了 3 句,也就是执行了前三个任务,第四个任务就死锁了。
而第三个任务的时候就已经把寿司吃完,所以执行第四个任务的时候会进入 mEmptyCondition.wait(); 阻塞,而阻塞的线程已经获取到了互斥锁( mLock.lock(); ),mEmptyCondition.wait() 只释放它自己的同步锁,并没有释放线程的互斥锁,所以制作寿司线程的 produce 方法也就无法执行,无法制作寿司,也没寿司可吃,最终进入死锁。

而 DEMO1 的 mEmptyCondition.await() 会释放互斥锁,让其他线程可以获得锁,从而把任务进行下去,当制作了寿司,唤醒 mEmptyCondition ,客人就可以继续开心地吃寿司啦 ^_^