线程通信 Condition BlockingQueue

当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但我们可以通过一些机制来包装线程的协调运行.

1.传统的线程通信

Object类有三个特殊的方法,分别是 wait(),notify(),notifyAll()这3个方法,他们不属于Thread,但和Thread却是息息相关.他们3个必须是由同步监视器对象来调用.

  1. 对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法总直接调用3个方法.
  2. 对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,就要用这个对象来调用.
  • wait():导致当前线程等待,直接其他线程用notify或者notifyAll唤醒.wait有三种1.无时间的,会一直等待.2.两种带时间的.
  • notify():唤醒在此同步监视器上等待的某个单个线程.选择是随意性的.
  • notifyAll():唤醒所有在此同步监视器上等待的线程.

看示例:

?
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
package org.credo.thread.control.synchronizedTest;
 
/**
  * 本代码是Account类提供存钱,取钱2个方法,因为这2个方法可能需要并发修改Account类的balance字段.
  * 所以都是用了synchronized来修饰方法.还是用了wait和notifyall来控制线程的协作.
  */
public class Account {
     private String accountNo;
     private double balance;
 
     // 没人存钱就为false,能save. 有就是true.
     private boolean haveMoney = false ;
 
     public Account() {
 
     }
 
     public Account(String accountNo, double balance) {
         this .accountNo = accountNo;
         this .balance = balance;
     }
 
     public synchronized void saveMoney( double saveMoney) {
         try {
             if (!haveMoney) {
                 // 执行存钱操作
                 System.out.println(Thread.currentThread().getName() + "存钱:" + saveMoney);
                 balance = +saveMoney;
                 System.out.println( "账户余额为:" + balance);
                 haveMoney = true ;
                 // 唤醒其他线程
                 this .notifyAll();
             } else {
                 // 当前线程等待
                 this .wait();
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
     public synchronized void getMoney( double getMoney, int i) {
         try {
             System.out.println( "i" + i);
             if (haveMoney) {
                 // 执行取钱操作
                 System.out.println(Thread.currentThread().getName() + "取钱:" + getMoney);
                 balance = -getMoney;
                 System.out.println( "账户余额为:" + balance);
                 haveMoney = false ;
             } else {
                 this .wait();
             }
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
 
     public String getAccountNo() {
         return accountNo;
     }
 
     public void setAccountNo(String accountNo) {
         this .accountNo = accountNo;
     }
 
     public boolean isHaveMoney() {
         return haveMoney;
     }
 
     public void setHaveMoney( boolean haveMoney) {
         this .haveMoney = haveMoney;
     }
 
     public double getBalance() {
         return balance;
     }
 
     @Override
     public int hashCode() {
         return this .accountNo.hashCode();
     }
 
     @Override
     public boolean equals(Object obj) {
         if ( this == obj) {
             return true ;
         }
         if (obj != null && obj.getClass() == Account. class ) {
             Account acc = (Account) obj;
             return acc.getAccountNo().equals(accountNo);
         }
         return false ;
     }
}
============================
?
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
package org.credo.thread.control.synchronizedTest;
 
/**
  * 取钱的线程操作类.
  */
public class GetMoneyThread extends Thread{
     private Account account;
     private double getMoney;
     
     public GetMoneyThread(String name,Account account, double getmoney){
         super .setName(name);
         this .account=account;
         this .getMoney=getmoney;
     }
     
     @Override
     public void run() {
         for ( int i= 0 ;i< 15 ;i++){
             account.getMoney(getMoney,i);
         }
     }
     
     public Account getAccount() {
         return account;
     }
     public void setAccount(Account account) {
         this .account = account;
     }
     public double getGetMoney() {
         return getMoney;
     }
     public void setGetMoney( double getMoney) {
         this .getMoney = getMoney;
     }
}
=============================================
?
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
package org.credo.thread.control.synchronizedTest;
 
/**
  *存钱的线程操作.
  */
public class SaveMoneyThread implements Runnable{
 
     @Override
     public void run() {
         for ( int i= 0 ;i< 15 ;i++){
             account.saveMoney(saveMoney);
         }
     }
 
     private Account account;
     private double saveMoney;
     
     public SaveMoneyThread(Account account, double saveMoney){
         this .account=account;
         this .saveMoney=saveMoney;
     }
 
     public Account getAccount() {
         return account;
     }
     public void setAccount(Account account) {
         this .account = account;
     }
     
     public double getSaveMoney() {
         return saveMoney;
     }
     
     public void setSaveMoney( double saveMoney) {
         this .saveMoney = saveMoney;
     }
}
========================
?
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.credo.thread.control.synchronizedTest;
 
public class MainTest {
 
     public static void main(String[] args) {
         Account acc= new Account( "zhaoqian" , 0 );
         new GetMoneyThread( "取钱人" , acc, 800 ).start();
         
         new Thread( new SaveMoneyThread(acc, 800 ), "存钱人1" ).start();
         new Thread( new SaveMoneyThread(acc, 800 ), "存钱人2" ).start();
         new Thread( new SaveMoneyThread(acc, 800 ), "存钱人3" ).start();
     }
}

//主程序可以启动任意多个存取线程.可以试验看出所有的取钱线程必须等存款线程存钱后才能继续执行.
//存钱的线程也必须等取出后才能继续执行.

运行上面代码后可以看到存取线程的交替执行.最后发现程序阻塞了,但不是死锁.

2.使用Condition控制线程通信

如果程序不使用synchronized关键字来保证同步,而直接用lock.那么系统中不存在隐式的同步监视器,就不能使用wait,notify,notifyAll方法进行线程通信了.

但使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象但无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他等待的线程.

这情况下,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能.

Condition实例绑定在Lock对象上,要获得需要调用Lock对象的newCondition方法.有3个方法:

  • await():类似于隐式同步监视器上的wait方法.该await方法有更多的重载方法.查阅API可知.
  • signal():任意唤醒一个Lock对象上等待的单个线程.只有当前线程放弃对该Lock对象的锁定后(使用await()方法),才可以自行被唤醒的线程.
  • signal():不解释.

以下的代码和上面的4个class文件一直,除了下面的Account类.代码如下:

?
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
113
package org.credo.thread.Condition;
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
  * 本代码是Account类提供存钱,取钱2个方法,因为这2个方法可能需要并发修改Account类的balance字段.
  * 所以都是用了lock来充当同步监视器.需要用Condition对象来暂停唤醒指定线程.
  */
public class Account {
     private String accountNo;
     private double balance;
 
     // 没人存钱就为false,能save. 有就是true.
     private boolean haveMoney = false ;
 
     public Account() {
 
     }
 
     public Account(String accountNo, double balance) {
         this .accountNo = accountNo;
         this .balance = balance;
     }
 
     //显示定义lock对象
     private final Lock lock= new ReentrantLock();
     //获取指定Lock对象对应的Condition
     private final Condition condition=lock.newCondition();
     
     public void saveMoney( double saveMoney) {
         lock.lock();
         try {
             if (!haveMoney) {
                 // 执行存钱操作
                 System.out.println(Thread.currentThread().getName() + "存钱:" + saveMoney);
                 balance = +saveMoney;
                 System.out.println( "账户余额为:" + balance);
                 haveMoney = true ;
                 // 唤醒其他线程
                 condition.signalAll();
             } else {
                 // 当前线程等待
                 condition.await();
             }
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             lock.unlock();
         }
     }
 
     public void getMoney( double getMoney, int i) {
         lock.lock();
         try {
             System.out.println( "i" + i);
             if (haveMoney) {
                 // 执行取钱操作
                 System.out.println(Thread.currentThread().getName() + "取钱:" + getMoney);
                 balance = -getMoney;
                 System.out.println( "账户余额为:" + balance);
                 haveMoney = false ;
                 // 唤醒其他线程
                 condition.signalAll();
             } else {
                 // 当前线程等待
                 condition.await();
             }
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             lock.unlock();
         }
     }
 
     public String getAccountNo() {
         return accountNo;
     }
 
     public void setAccountNo(String accountNo) {
         this .accountNo = accountNo;
     }
 
     public boolean isHaveMoney() {
         return haveMoney;
     }
 
     public void setHaveMoney( boolean haveMoney) {
         this .haveMoney = haveMoney;
     }
 
     public double getBalance() {
         return balance;
     }
 
     @Override
     public int hashCode() {
         return this .accountNo.hashCode();
     }
 
     @Override
     public boolean equals(Object obj) {
         if ( this == obj) {
             return true ;
         }
         if (obj != null && obj.getClass() == Account. class ) {
             Account acc = (Account) obj;
             return acc.getAccountNo().equals(accountNo);
         }
         return false ;
     }
}

结果和上面传统的那个一样.

3.使用阻塞队列(BlockingQueue)控制线程通信

小例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.credo.thread.BlockingQueue;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
 
public class SmallTest {
 
     public static void main(String[] args) throws InterruptedException {
         BlockingQueue<String> bq= new ArrayBlockingQueue<>( 2 );
         System.out.println( "1" );
         bq.put( "java" );
         System.out.println( "2" );
         bq.put( "C++" );
         System.out.println( "3" );
         bq.put( "C#" );
         System.out.println( "4" );
     }
 
}

BlockingQueue提供2个阻塞的方法:

  • 1.put(E e):把E放入到BlockingQueue队尾中,如果队列元素已满,就阻塞该线程.
  • 2.take():从头部取出元素,如果队列为空,就阻塞该线程.

BlockingQueue继承了Queue接口,自然可以使用Queue的方法.分为3组:

  • 在队列尾部插入元素.包括add(E e),offer(E e),put(E e),队列满的时候,分别是抛异常,返回false,阻塞队列.
  • 在队列头部删除并返回删除的元素,包括remove(),poll().take(),队列已空的时候,分别是抛异常,返回false,阻塞队列.
  • 在队列头部取出但不删除元素,包括element(),peek方法.队列已空的时候,分别是抛异常,返回false,

BlockingQueue包含5个实现类(javaSE 7):

  • ArrayBlockingQueue:基于数组实现的BlockingQueue.
  • LinkedBlockingQueue:基于链表实现的BlockingQueue.
  • PriorityBlockingQueue:查API去.不是标准的注射队列.取元素的时候不是取出在队列中存在时间最长的,而是队列中最小的元素.
  • SynchronousQueue:同步队列,对该队列的存取操作必须交替进行.
  • DelayQueue:特殊的BlockingQueue,底层基于PriorityBlockingQueue实现.具体API去.

BigTest:

?
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
package org.credo.thread.BlockingQueue;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
 
public class BigTest {
 
     public static void main(String[] args) {
         //创建一个容量为1的BlockingQueue
         BlockingQueue<String> bq= new ArrayBlockingQueue<String>( 1 );
         //启动三个生产者线程
         new Producer(bq).start();
         new Producer(bq).start();
         new Producer(bq).start();
         //启动一个消费者线程
         new Consumer(bq).start();
     }
}
//内部类生产者
class Producer extends Thread{
     private BlockingQueue<String> bq;
     public Producer(BlockingQueue<String> bq){
         this .bq=bq;
     }
     public void run(){
         String[] strArr= new String[]{ "Java" , "C++" , "C#" };
         for ( int i= 0 ;i< 9999 ;i++){
             System.out.println(getName()+ "生产" );
             try {
                 Thread.sleep( 200 );
                 //尝试放入元素,如果队列已经满了,则线程堵塞
                 bq.put(strArr[i% 3 ]);
             } catch (Exception e) {
                 e.printStackTrace();
             }
             System.out.println(getName()+ "生产完成:" +bq);
         }
     }
}
//内部类消费者
class Consumer extends Thread{
     private BlockingQueue<String> bq;
     public Consumer(BlockingQueue<String> bq){
         this .bq=bq;
     }
     public void run(){
         while ( true ){
             System.out.println(getName()+ "消费!" );
             try {
                 Thread.sleep( 200 );
                 bq.take();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(getName()+ "消费完成:" +bq);
         }
     }
}

上面启动3个生产的线程往BlockingQueue放元素,启动一个去取.因为BlockingQueue为1,所以无法连续放入.必须等取出一个,3个生产者之一才能放进去一个.可以运行看看.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值