5. 线程通信
5.1 Lock 接口
是什么:Lock
实现提供比使用 synchronized
方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象 Condition
Lock
接口的实现 ReentrantLock
可重入锁
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
synchronized 与 Lock 的区别两者区别:
- 首先 synchronized 是 java 内置关键字,在 jvm 层面,Lock 是个 java 类(接口)
- synchronized 无法判断是否获取锁的状态,Lock 可以判断是否获取到锁
- synchronized 会自动释放锁(a线程执行完同步代码会释放锁;b线程执行过程中发生异常会释放锁),Lock 需在 finally 中手工释放锁(unlock() 方法释放锁),否则容易造成线程死锁
- 用 synchronized 关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而 Lock 锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了
- synchronized 的锁可重入、不可中断、非公平,而 Lock 锁可重入、可判断、可公平(两者皆可)
- Lock 锁适合大量同步的代码的同步问题,synchronized 锁适合代码少量的同步问题。
5.2 线程通信
- 生产者+消费者
- 通知等待唤醒机制
多线程编程模板:
- 在高内聚、低耦合的前提下,线程 操作(对外暴露的调用方法,自身的方法) 资源类
高内聚:自己应带的东西自己带着,并对外暴露(空调有制冷和制热是出厂就带着的,但是人不带着制冷和制热)
低耦合(拆分):eg:前后端分离、淘宝与顺丰之间的关系,但是淘宝内部没有与顺丰相关的、顺丰内部也没有与淘宝相关的
- 判断 —> 干活 —> 通知
- 防止虚假唤醒用 while
- 注意修改标志位
案例:现在两个线程,可以操作初始值为零的一个变量,实现一个线程对该变量加1,一个线程对该变量减1,交替,来10轮。
Synchronized
实现
// 资源类
class AirConditioner {
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 1. 判断
while (number != 0) {
this.wait();
}
// 2. 干活
++number;
SmallTool.printTimeAndThread(String.valueOf(number));
// 3. 通知
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1. 判断
while (number == 0) {
this.wait();
}
// 2. 干活
--number;
SmallTool.printTimeAndThread(String.valueOf(number));
// 3. 通知
this.notifyAll();
}
}
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
airConditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
airConditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
Java8 新版本实现
Condition
代码实现
// 资源类
class AirConditioner {
private int number = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
// 1. 判断
while (number != 0) {
condition.await();
}
// 2. 干活
++number;
SmallTool.printTimeAndThread(String.valueOf(number));
// 3. 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
// 1.判断
while (number == 0) {
condition.await();
}
// 2.干活
number--;
SmallTool.printTimeAndThread(String.valueOf(number));
// 3.通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
AirConditioner airConditioner = new AirConditioner();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airConditioner.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airConditioner.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airConditioner.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
airConditioner.decrement();
}
}, "D").start();
}
5.3 线程间定制化调用通信
案例:多线程之间按顺序调用,实现A->B->C,三个线程启动,要求如下:AA打印1次,BB打印2次,CC打印3次,接着,AA打印1次,BB打印2次,CC打印3次,来4轮
// 资源类
class ShareResource {
/**
* 1:A 2:B 3:C
*/
private int number = 1;
private final Lock lock = new ReentrantLock();
/**
* A 的监视器
*/
private final Condition condition1 = lock.newCondition();
/**
* B 的监视器
*/
private final Condition condition2 = lock.newCondition();
/**
* C 的监视器
*/
private final Condition condition3 = lock.newCondition();
public void print1() {
lock.lock();
try {
// 1. 判断
while (number != 1) {
condition1.await();
}
// 2. 干活
SmallTool.printTimeAndThread(String.valueOf(number));
// 修改标志位
number = 2;
// 3. 实现精确通知
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print2() {
lock.lock();
try {
// 1. 判断
while (number != 2) {
condition2.await();
}
// 2. 干活
for (int i = 1; i <= 2; i++) {
SmallTool.printTimeAndThread(String.valueOf(number));
}
// 修改标志位
number = 3;
// 3. 实现精确通知
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print3() {
lock.lock();
try {
// 1. 判断
while (number != 3) {
condition3.await();
}
// 2. 干活
for (int i = 1; i <= 3; i++) {
SmallTool.printTimeAndThread(String.valueOf(number));
}
// 修改标志位
number = 1;
// 3. 实现精确通知
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 4; i++) {
shareResource.print1();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 4; i++) {
shareResource.print2();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 4; i++) {
shareResource.print3();
}
}, "C").start();
}
// 结果
1672558974888 | 24 | A | 1
1672558974889 | 25 | B | 2
1672558974889 | 25 | B | 2
1672558974889 | 26 | C | 3
1672558974889 | 26 | C | 3
1672558974889 | 26 | C | 3
1672558974889 | 24 | A | 1
1672558974889 | 25 | B | 2
1672558974889 | 25 | B | 2
1672558974890 | 26 | C | 3
1672558974890 | 26 | C | 3
1672558974890 | 26 | C | 3
1672558974890 | 24 | A | 1
1672558974890 | 25 | B | 2
1672558974890 | 25 | B | 2
1672558974890 | 26 | C | 3
1672558974890 | 26 | C | 3
1672558974890 | 26 | C | 3
1672558974890 | 24 | A | 1
1672558974890 | 25 | B | 2
1672558974890 | 25 | B | 2
1672558974890 | 26 | C | 3
1672558974890 | 26 | C | 3
1672558974890 | 26 | C | 3
更多文章在我的语雀平台:https://www.yuque.com/ambition-bcpii/muziteng