初识Java多线程之线程间的通信

使用线程通信完成交替打印1-100

实现原理

实现线程间的交替打印主要是通过wait()notify()两个方法。下面我们来讲一下几个相关方法的作用

注:以下3个方法都必须在同步代码块或同步方法中调用,否则会抛出java.lang.IllegalMonitorStateException异常

  1. wait():一旦执行该方法,当前线程就会进入阻塞状态,并释放同步监视器(意味着其他线程可以进入)。
  2. notify():一旦执行该方法,就会唤醒一个wait的线程,如果有多个线程wait,则唤醒优先级最高的那个。
  3. notifyAll():一旦执行该方法,会唤醒所有被wait的线程。

实现代码

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
package com.example.socket.multithreading;

class RunnableImpl2 implements Runnable {
private int num = 1;

@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}

public class CommunicationThread {
public static void main(String[] args) {
Runnable runnable = new RunnableImpl2();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}

}

分析代码执行过程

注:以下描述的同步监视器等同于锁

我们以A、B来代替代码中的两个线程,首先两个线程其他,假设A抢到了CPU执行权,则执行run方法,首先线程A调用notify()方法,此时没有线程wait,所以不会唤醒任何线程,然后A打印出1,并执行num++,最后线程A调用wait()方法。此时线程A进入阻塞状态,释放同步监视器,线程B可以拿到锁。然后线程B调用notify()方法唤醒线程A,由于锁没释放,所以线程A暂时不能执行。最后线程B调用wait()方法,进入阻塞状态,释放同步监视器。然后线程A可以拿到锁,执行相应代码,如此循环往复就会出现交替打印的效果。

sleep与wait方法有什么区别?

相同点

执行这两个方法后都会使线程进入阻塞状态

不同点

  1. sleep方法声明在Thread类中,而wait方法声明咋Object类当中。
  2. sleep方法可以在任意场景当中调用,而wait方法只能在同步代码块或同步方法中调用。
  3. sleep方法调用后不会释放同步监视器,而wait方法调用后会释放同步监视器。

生产者消费者问题

问题描述

生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

实现代码

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
package com.example.socket.multithreading;

/**
* 负责通知生产和消费的店员
*/
class Clerk {

private static Integer num = 0;

public synchronized void product() {
if (num < 20) {
num++;
System.out.println(Thread.currentThread().getName() + "开始生产第" + num + "件商品");
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public synchronized void customer() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "开始消费第" + num + "件商品");
num--;
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

}

/**
* 生产者
*/
class Product implements Runnable {

private Clerk clerk;

public Product(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println("开始生产:");
while (true) {
clerk.product();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

/**
* 消费者
*/
class Customer implements Runnable {
private Clerk clerk;

public Customer(Clerk clerk) {
this.clerk = clerk;
}

@Override
public void run() {
System.out.println("开始消费:");
while (true) {
clerk.customer();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public class ProductAndCustomer {

public static void main(String[] args) {
Clerk clerk = new Clerk();
Thread thread = new Thread(new Customer(clerk));
Thread thread1 = new Thread(new Product(clerk));
thread.setName("消费者");
thread1.setName("生产者");
thread.start();
thread1.start();
}
}

该段代码实现原理基本与交替打印类似,不再详述。