线程通信

如果使用synchronized关键词,实现线程同步,可以使用Object类提供的三个方法,这三个方法必须使用同步监视器来调用,对于同步方法,可以直接使用,因为该类默认实例this就是同步监视器,对于同步代码块,必须使用同步监视器对象来调用这三个方法。

  • wait():导致当前线程等待,直到其他线程调用该同步监视器notify()方法或者notifyAll()方法来唤醒该线程。该方法有三种形式:无参,带毫秒参数,带毫秒和毫微妙参数。调用wait方法的当前线程会释放对同步监视器的锁定
  • notify():唤醒在此同步监视器等待的单个线程,如果很多线程都在此同步监视器上等待,就唤醒其中一个。选择是任意的,只有当前线程放弃对该同步监视器锁定后,才可以执行被唤醒的线程
  • notifyAll():唤醒在此同步监视器上等待的所有线程,只有当前线程放弃对该同步监视器锁定后,才可以执行被唤醒的线程

类似操作系统中的PV操作,阻塞线程和唤醒线程。

例子:

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
114
115
116
117
118
119
120
121
122
123
124
package work;


public class Main {

public static void main(String[] args) throws InterruptedException {

Accout acc = new Accout("123456w", 0);

Thread thread1 = new TakeMoney("取钱人", acc, 100);
Thread thread2 = new SaveMoney("存钱人", acc, 100);
Thread thread3 = new SaveMoney("存钱人", acc, 100);
Thread thread4 = new SaveMoney("存钱人", acc, 100);


thread1.start();
thread2.start();
thread3.start();
thread4.start();


}

}

class TakeMoney extends Thread {

private Accout accout;
private double money;

public TakeMoney(String name, Accout accout, double money) {
super(name);
this.accout = accout;
this.money = money;
}

@Override
public void run() {

for(; true ;){
accout.take(this, accout, money);
}
}
}

class SaveMoney extends Thread {

private Accout accout;
private double money;

public SaveMoney(String name, Accout accout, double money) {
super(name);
this.accout = accout;
this.money = money;
}

public Accout getAccout() {
return accout;
}

public double getMoney() {
return money;
}

@Override
public void run() {

for(;true;){
accout.save(this, accout, money);
}
}
}


class Accout {

public synchronized void take(Thread thread, Accout accout, double money) {

if(flag == false) {

try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

else {
if(accout.getBalance() >= money) {
System.out.println(thread.getName() + "取钱成功,取出" + money);
balance = balance - money;
System.out.println("当前余额为:" + accout.getBalance());

flag = false;
notifyAll();

}
}

}

public synchronized void save(Thread thread, Accout accout, double money) {

if(flag == true) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {

System.out.println(thread.getName() + "存钱成功,存进" + money);
balance = balance + money;
System.out.println("当前余额为:" + accout.getBalance());

flag = true;
notifyAll();
}
}
}




使用Condition控制线程通信

如果程序不使用synchronized关键词保证同步,而是直接使用Lock对象,那么就没有隐式同步监视器,就不能使用wait、notify、notifyAll方法进行线程通信。

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

Condition实例被绑定在一个Lock对象上,要获得Lock实例的Condition实例,可以调用Lock对象的newCondition方法,Condition类提供了三个方法:

  1. await():类似隐式同步监视器的wait方法,可以导致当前线程等待,直到其他线程调用该Condition的signal方法或signalAll方法来唤醒该线程,该await方法有多个变体,如long awaitNanos(long nanosTimeout)、void awaitUninterruptibly()、awaitUntil(Date deadline)可以完成更丰富的操作
  2. signal():唤醒在此Lock对象上等待的单个线程,如果有多个线程在等待,那么选择任意一个线程来唤醒。只有当前线程放弃对Lock对象的锁定后(使用await方法),才可以执行被唤醒的线程
  3. signalALL():唤醒在此Lock对象上等待的所有线程,只有当前线程放弃对Lock对象的锁定后(使用await方法),才可以执行被唤醒的线程

注意!!!!!:无论是使用wait方法还是await方法使线程阻塞后,再次唤醒线程后,会从上次阻塞的位置继续开始执行,而不是重新进入同步代码块.

使用阻塞队列BlockingQueue控制线程通信

Java提供了一个BlockingQueue接口,它虽然是Queue的子接口,但它的主要作用并不是作为容器,而是作为线程同步的工具,BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。

程序的两个线程通过交替向BlockingQueue中放入、取出元素,可以很好的控制线程通信。

BlockingQueue提供了两个支持阻塞的方法:

  • put(E e):尝试把E元素放入BlockingQueue中,如果该队列元素已满,则阻塞该线程
  • take():尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程

BlockingQueue继承自Queue,当然也可以使用Queue的方法,这些方法归纳起来可分为三组

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

BlockingQueue包括五个实现类

  • ArrayBlockingQueue:基于数组实现的BlockingQueue队列
  • LinkedBlockingQueue:基于链表实现的BlockingQueue队列
  • PriorityBlockingQueue:它不是标准的阻塞队列,该队列调用remove、poll、take方法取出元素时,并不是取出队列中存在时间最长的,而是取出队列中最小的元素,PriorityBlockingQueue判断元素的大小可以根据元素本身大小来自然排序(实现Comparable接口),也可以使用Conparator接口进行定制排序
  • SynchronizedQueue:同步队列,对该队列的存、取操作必须交替进行
  • DelayQueue:是一个特殊的阻塞队列,底层基于PriorityBlockingQueue实现,不过Delay要求集合元素都实现Delay接口(该接口只有一个long getDelay()方法),DelayQueue根据集合元素的getDelay的返回值进行排序

线程通信
https://zhaoquaner.github.io/2022/05/11/Java/多线程/线程通信/
更新于
2022年5月22日
许可协议