线程间通信模型

线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。

我们来基本一道面试常见的题目来分析

场景:两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

volatile 方案

基于volatile关键字来实现线程间相互通信是使用共享内存的思想

大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式

首先定义一个实现类DemoClass

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
public class DemoClass {
/**
* 定义一个全局的变量,负责加一和减一
*/
private int number = 0;

/**
* 定义一个线程间通信的对象(通过volatile关键字,实现两个线程的共同监控)
* true执行加1,false执行减1
*/
private static volatile boolean flag = true;


/**
* 负责记录循环的次数
*/
private int loop = 0;


/**
* 加一
*/
public void increment() {
try {
// volatile没有锁,所以可以直接死循环
while (true) {
if (flag) {
number++;
System.out.println("------当前第" + loop + "次循环-----");
System.out.println(Thread.currentThread().getName() + "执行了加一,值为:" + number);
flag = false;
Thread.sleep(2000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 减一
*/
public void decrement() {
try {
while (true) {
if (!flag) {
number--;
System.out.println(Thread.currentThread().getName() + "执行了减一,值为:" + number);
loop++;
flag = true;
Thread.sleep(2000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestVolatile {

public static void main(String[] args) {

DemoClass demoClass = new DemoClass();

new Thread(() -> {
demoClass.increment();
}, "线程一").start();

new Thread(() -> {
demoClass.decrement();
}, "线程二").start();
}
}

synchronized 方案

  • 等待池:假设一个线程 A 调用了某个对象的 wait()方法,线程 A 就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
  • 锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待
  • 区别:notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
  • 关键字synchronizedwait() / notify() 这两个方法一起使用用来实现 等待/通知模式

实现类DemoClass

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
public class DemoClass {

private int number = 0;

/**
* 加1
*/
public synchronized void increment() {
try {
// 先判断number是否可以加
while (number != 0) {
// 否则进入等待池等待
this.wait();
}
number++;
System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------,值为:" + number);
Thread.sleep(2000);
// 唤醒对象的等待池中的所有线程
this.notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 减一
*/
public synchronized void decrement() {
try {
// 先判断number是否可以减
while (number == 0) {
// 否则进入等待池等待
this.wait();
}
number--;
System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------,值为:" + number);
Thread.sleep(2000);
// 唤醒对象的等待池中的所有线程
this.notifyAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestSynchronized {

public static void main(String[] args) {

DemoClass demoClass = new DemoClass();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demoClass.increment();
}
}, "线程一").start();

new Thread(() -> {
for (int i = 0; i < 5; i++) {
demoClass.decrement();
}
}, "线程二").start();
}
}

Lock 方案

Condition类与 await()signal()/signalAll()方法一起使用也可以实现等待通知模式

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
public class DemoClass {
/**
* 定义全局的操作变量
*/
private int number = 0;

/**
* 定义一把锁
*/
private Lock lock = new ReentrantLock();

/**
* 定义一把钥匙
* Lock的newContition()方法返回Condition对象,Condition类也可以实现等待/通知模式。
*/
private Condition condition = lock.newCondition();

/**
* 加1
*/
public void increment() {
try {
lock.lock();
while (number != 0) {
// 调用await()方法后当前线程会释放这个锁
condition.await();
}
number++;
System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------,值为:" + number);
// 调用singal()方法从当前Condition对象的等待队列中,唤醒所有线程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

/**
* 减一
*/
public void decrement() {
try {
lock.lock();
while (number == 0) {
condition.await();
}
number--;
System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------,值为:" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TestLock {

public static void main(String[] args) {

com.atguigu.demo7.DemoClass demoClass = new DemoClass();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demoClass.increment();
}
}, "线程一").start();

new Thread(() -> {
for (int i = 0; i < 5; i++) {
demoClass.decrement();
}
}, "线程二").start();
}
}

线程间定制化通信

案例介绍

实现 A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮

实现流程

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
public class DemoClass {
/**
* 定义一个全局的通信对象
* 0--A线程打印A
* 1--B线程打印B
* 2--C线程打印C
*/
private int number = 0;

/**
* 定义一把全局锁
*/
private Lock lock = new ReentrantLock();

/**
* 声明A线程的钥匙
*/
private Condition conditionA = lock.newCondition();

/**
* 声明B线程的钥匙
*/
private Condition conditionB = lock.newCondition();

/**
* 声明C线程的钥匙
*/
private Condition conditionC = lock.newCondition();

/**
* A打印5次
*/
public void printA(int j) {
try {
lock.lock();
while (number != 0) {
conditionA.await();
}
System.out.println("第" + j + "轮开始");
System.out.println(Thread.currentThread().getName() + "输出A");
//输出5次A
for (int i = 0; i < 5; i++) {
System.out.print("A");
}
System.out.println();
//开始打印B
number = 1;
//唤醒B
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

/**
* B打印10次
*/
public void printB() {
try {
lock.lock();
while (number != 1) {
conditionB.await();
}
System.out.println(Thread.currentThread().getName() + "输出B");
//输出10次B
for (int i = 0; i < 10; i++) {
System.out.print("B");
}
System.out.println();
//开始打印C
number = 2;
//唤醒C
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

/**
* C打印15次
*/
public void printC() {
try {
lock.lock();
while (number != 2) {
conditionC.await();
}
System.out.println(Thread.currentThread().getName() + "输出C");
//输出15次C
for (int i = 0; i < 15; i++) {
System.out.print("C");
}
System.out.println();
System.out.println("-----------------------------------------");
//开始打印A
number = 0;
//唤醒A
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

测试类

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
public class Test {

public static void main(String[] args) {

DemoClass demoClass = new DemoClass();

new Thread(() -> {
// 循环10轮
for (int i = 1; i <= 10; i++) {
demoClass.printA(i);
}
}, "A线程").start();

new Thread(() -> {
for (int i = 1; i <= 10; i++) {
demoClass.printB();
}
}, "B线程").start();

new Thread(() -> {
for (int i = 1; i <= 10; i++) {
demoClass.printC();
}
}, "C线程").start();
}
}