gpt4 book ai didi

Java并发之条件阻塞Condition的应用代码示例

转载 作者:qq735679552 更新时间:2022-09-27 22:32:09 27 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章Java并发之条件阻塞Condition的应用代码示例由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.

本文研究的主要是Java并发之条件阻塞Condition的应用示例代码,具体如下.

Condition将Object监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用.

1. Condition的基本使用

  。

  由于Condition可以用来替代wait、notify等方法,所以可以对比着之前写过的线程间通信的代码来看,再来看一下原来那个问题:

有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次……如此往返执行50次.

  之前用wait和notify来实现的,现在用Condition来改写一下,代码如下:

?
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
public class ConditionCommunication {
     public static void main(String[] args) {
         Business bussiness = new Business();
         new Thread( new Runnable() {
             // 开启一个子线程
             @Override
                       public void run() {
                 for ( int i = 1 ; i <= 50 ; i++) {
                     bussiness.sub(i);
                 }
             }
         }
         ).start();
         // main方法主线程
         for ( int i = 1 ; i <= 50 ; i++) {
             bussiness.main(i);
         }
     }
}
class Business {
     Lock lock = new ReentrantLock();
     Condition condition = lock.newCondition();
     //Condition是在具体的lock之上的
     private Boolean bShouldSub = true ;
     public void sub( int i) {
         lock.lock();
         try {
             while (!bShouldSub) {
                 try {
                     condition.await();
                     //用condition来调用await方法
                 }
                 catch (Exception e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
             }
             for ( int j = 1 ; j <= 10 ; j++) {
                 System.out.println( "sub thread sequence of " + j
                             + ", loop of " + i);
             }
             bShouldSub = false ;
             condition.signal();
             //用condition来发出唤醒信号,唤醒某一个
         }
         finally {
             lock.unlock();
         }
     }
     public void main( int i) {
         lock.lock();
         try {
             while (bShouldSub) {
                 try {
                     condition.await();
                     //用condition来调用await方法
                 }
                 catch (Exception e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
             }
             for ( int j = 1 ; j <= 10 ; j++) {
                 System.out.println( "main thread sequence of " + j
                             + ", loop of " + i);
             }
             bShouldSub = true ;
             condition.signal();
             //用condition来发出唤醒信号么,唤醒某一个
         }
         finally {
             lock.unlock();
         }
     }
}

从代码来看,Condition的使用时和Lock一起的,没有Lock就没法使用Condition,因为Condition是通过Lock来new出来的,这种用法很简单,只要掌握了synchronized和wait、notify的使用,完全可以掌握Lock和Condition的使用.

2. Condition的拔高

  。

2.1 缓冲区的阻塞队列 。

  上面使用Lock和Condition来代替synchronized和Object监视器方法实现了两个线程之间的通信,现在再来写个稍微高级点应用:模拟缓冲区的阻塞队列。 什么叫缓冲区呢?举个例子,现在有很多人要发消息,我是中转站,我要帮别人把消息发出去,那么现在我  就需要做两件事,一件事是接收用户发过来的消息,并按顺序放到缓冲区,另一件事是从缓冲区中按顺序取出用户发过来的消息,并发送出去.

  现在把这个实际的问题抽象一下:缓冲区即一个数组,我们可以向数组中写入数据,也可以从数组中把数据取走,我要做的两件事就是开启两个线程,一个存数据,一个取数据。但是问题来了,如果缓冲区满了,说明接收的消息太多了,即发送过来的消息太快了,我另一个线程还来不及发完,导致现在缓冲区没地方放了,那么此时就得阻塞存数据这个线程,让其等待;相反,如果我转发的太快,现在缓冲区所有内容都被我发完了,还没有用户发新的消息来,那么此时就得阻塞取数据这个线程.

  好了,分析完了这个缓冲区的阻塞队列,下面就用Condition技术来实现一下:

?
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
class Buffer {
     final Lock lock = new ReentrantLock();
     //定义一个锁
     final Condition notFull = lock.newCondition();
     //定义阻塞队列满了的Condition
     final Condition notEmpty = lock.newCondition();
     //定义阻塞队列空了的Condition
     final Object[] items = new Object[ 10 ];
     //为了下面模拟,设置阻塞队列的大小为10,不要设太大
     int putptr, takeptr, count;
     //数组下标,用来标定位置的
     //往队列中存数据
     public void put(Object x) throws InterruptedException {
         lock.lock();
         //上锁
         try {
             while (count == items.length) {
                 System.out.println(Thread.currentThread().getName() + " 被阻塞了,暂时无法存数据!" );
                 notFull.await();
                 //如果队列满了,那么阻塞存数据这个线程,等待被唤醒
             }
             //如果没满,按顺序往数组中存
             items[putptr] = x;
             if (++putptr == items.length) //这是到达数组末端的判断,如果到了,再回到始端
             putptr = 0 ;
             ++count;
             //消息数量
             System.out.println(Thread.currentThread().getName() + " 存好了值: " + x);
             notEmpty.signal();
             //好了,现在队列中有数据了,唤醒队列空的那个线程,可以取数据啦
         }
         finally {
             lock.unlock();
             //放锁
         }
     }
     //从队列中取数据
     public Object take() throws InterruptedException {
         lock.lock();
         //上锁
         try {
             while (count == 0 ) {
                 System.out.println(Thread.currentThread().getName() + " 被阻塞了,暂时无法取数据!" );
                 notEmpty.await();
                 //如果队列是空,那么阻塞取数据这个线程,等待被唤醒
             }
             //如果没空,按顺序从数组中取
             Object x = items[takeptr];
             if (++takeptr == items.length) //判断是否到达末端,如果到了,再回到始端
             takeptr = 0 ;
             --count;
             //消息数量
             System.out.println(Thread.currentThread().getName() + " 取出了值: " + x);
             notFull.signal();
             //好了,现在队列中有位置了,唤醒队列满的那个线程,可以存数据啦
             return x;
         }
         finally {
             lock.unlock();
             //放锁
         }
     }
}

这个程序很经典,我从官方JDK文档中拿出来的,然后加了注释。程序中定义了两个Condition,分别针对两个线程,等待和唤醒分别用不同的Condition来执行,思路很清晰,程序也很健壮。可以考虑一个问题,为啥要用两个Codition呢?之所以这么设计肯定是有原因的,如果用一个Condition,现在假设队列满了,但是有2个线程A和B同时存数据,那么都进入了睡眠,好,现在另一个线程取走一个了,然后唤醒了其中一个线程A,那么A可以存了,存完后,A又唤醒一个线程,如果B被唤醒了,那就出问题了,因为此时队列是满的,B不能存的,B存的话就会覆盖原来还没被取走的值,就因为使用了一个Condition,存和取都用这个Condition来睡眠和唤醒,就乱了套。到这里,就能体会到这个Condition的用武之地了,现在来测试一下上面的阻塞队列的效果:

?
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
public class BoundedBuffer {
     public static void main(String[] args) {
         Buffer buffer = new Buffer();
         for ( int i = 0 ; i < 5 ; i ++) {
             //开启5个线程往缓冲区存数据
             new Thread( new Runnable() {
                 @Override
                         public void run() {
                     try {
                         buffer.put( new Random().nextint( 1000 ));
                         //随机存数据
                     }
                     catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
             ).start();
         }
         for ( int i = 0 ; i < 10 ; i ++) {
             //开启10个线程从缓冲区中取数据
             new Thread( new Runnable() {
                 @Override
                         public void run() {
                     try {
                         buffer.take();
                         //从缓冲区取数据
                     }
                     catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
             ).start();
         }
     }
}

我故意只开启5个线程存数据,10个线程取数据,就是想让它出现取数据被阻塞的情况发生,看运行的结果:

Thread-5 被阻塞了,暂时无法取数据! Thread-10 被阻塞了,暂时无法取数据! Thread-1 存好了值: 755 Thread-0 存好了值: 206 Thread-2 存好了值: 741 Thread-3 存好了值: 381 Thread-14 取出了值: 755 Thread-4 存好了值: 783 Thread-6 取出了值: 206 Thread-7 取出了值: 741 Thread-8 取出了值: 381 Thread-9 取出了值: 783 Thread-5 被阻塞了,暂时无法取数据! Thread-11 被阻塞了,暂时无法取数据! Thread-12 被阻塞了,暂时无法取数据! Thread-10 被阻塞了,暂时无法取数据! Thread-13 被阻塞了,暂时无法取数据! 。

  从结果中可以看出,线程5和10抢先执行,发现队列中没有,于是就被阻塞了,睡在那了,直到队列中有新的值存入才可以取,但是它们两运气不好,存的数据又被其他线程给抢先取走了,哈哈……可以多运行几次。如果想要看到存数据被阻塞,可以将取数据的线程设置少一点,这里我就不设了.

2.2 两个以上线程之间的唤醒 。

  还是原来那个题目,现在让三个线程来执行,看一下题目:

有三个线程,子线程1先执行10次,然后子线程2执行10次,然后主线程执行5次,然后再切换到子线程1执行10次,子线程2执行10次,主线程执行5次……如此往返执行50次.

  如过不用Condition,还真不好弄,但是用Condition来做的话,就非常方便了,原理很简单,定义三个Condition,子线程1执行完唤醒子线程2,子线程2执行完唤醒主线程,主线程执行完唤醒子线程1。唤醒机制和上面那个缓冲区道理差不多,下面看看代码吧,很容易理解.

?
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
111
112
public class ThreeConditionCommunication {
     public static void main(String[] args) {
         Business bussiness = new Business();
         new Thread( new Runnable() {
             // 开启一个子线程
             @Override
                       public void run() {
                 for ( int i = 1 ; i <= 50 ; i++) {
                     bussiness.sub1(i);
                 }
             }
         }
         ).start();
         new Thread( new Runnable() {
             // 开启另一个子线程
             @Override
                   public void run() {
                 for ( int i = 1 ; i <= 50 ; i++) {
                     bussiness.sub2(i);
                 }
             }
         }
         ).start();
         // main方法主线程
         for ( int i = 1 ; i <= 50 ; i++) {
             bussiness.main(i);
         }
     }
     static class Business {
         Lock lock = new ReentrantLock();
         Condition condition1 = lock.newCondition();
         //Condition是在具体的lock之上的
         Condition condition2 = lock.newCondition();
         Condition conditionMain = lock.newCondition();
         private int bShouldSub = 0 ;
         public void sub1( int i) {
             lock.lock();
             try {
                 while (bShouldSub != 0 ) {
                     try {
                         condition1.await();
                         //用condition来调用await方法
                     }
                     catch (Exception e) {
                         // TODO Auto-generated catch block
                         e.printStackTrace();
                     }
                 }
                 for ( int j = 1 ; j <= 10 ; j++) {
                     System.out.println( "sub1 thread sequence of " + j
                                   + ", loop of " + i);
                 }
                 bShouldSub = 1 ;
                 condition2.signal();
                 //让线程2执行
             }
             finally {
                 lock.unlock();
             }
         }
         public void sub2( int i) {
             lock.lock();
             try {
                 while (bShouldSub != 1 ) {
                     try {
                         condition2.await();
                         //用condition来调用await方法
                     }
                     catch (Exception e) {
                         // TODO Auto-generated catch block
                         e.printStackTrace();
                     }
                 }
                 for ( int j = 1 ; j <= 10 ; j++) {
                     System.out.println( "sub2 thread sequence of " + j
                                   + ", loop of " + i);
                 }
                 bShouldSub = 2 ;
                 conditionMain.signal();
                 //让主线程执行
             }
             finally {
                 lock.unlock();
             }
         }
         public void main( int i) {
             lock.lock();
             try {
                 while (bShouldSub != 2 ) {
                     try {
                         conditionMain.await();
                         //用condition来调用await方法
                     }
                     catch (Exception e) {
                         // TODO Auto-generated catch block
                         e.printStackTrace();
                     }
                 }
                 for ( int j = 1 ; j <= 5 ; j++) {
                     System.out.println( "main thread sequence of " + j
                                   + ", loop of " + i);
                 }
                 bShouldSub = 0 ;
                 condition1.signal();
                 //让线程1执行
             }
             finally {
                 lock.unlock();
             }
         }
     }
}

代码看似有点长,但是是假象,逻辑非常简单。关于线程中的Condition技术就总结这么多吧.

总结

  。

以上就是本文关于Java并发之条件阻塞Condition的应用代码示例的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持! 。

原文链接:http://blog.csdn.net/eson_15/article/details/51559860 。

最后此篇关于Java并发之条件阻塞Condition的应用代码示例的文章就讲到这里了,如果你想了解更多关于Java并发之条件阻塞Condition的应用代码示例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com