共享模型之管程
共享模型之管程
共享带来的问题
两个线程对一个初始值是 0 的静态变量,一个做自增操作,一个做自减操作,结果还是 0 吗?
static int num = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
num++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
num--;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("num: {}", num);
}
我们的期望值是 0, 但结果:
21:43:16.824 [main] DEBUG com.byteframework.learn.sync.Test1 - num: -533
这就是线程切换带来的安全问题。
临界区 Critical Section
一段代码块内,如果存在对共享资源的多线程读写操作,称这段代码块为临界区
临界区代码举例:
static int counter = 0;
static void increment()
// 临界区
{
counter++;
}
static void decrement()
// 临界区
{
counter--;
}
竞态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
synchronized 解决方案
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案: sysnchronized 、Lock
- 非阻塞式的解决方案: 原子变量
本次使用 synchronized 来解决上述问题,俗称对象锁,它采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其他线程想再获取这个对象锁时就会被阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换。
注意
虽然 java 中互斥和同步都可以采用 sysnchronized 关键字来完成,但他们还是有区别的:
- 互斥是保证临界区的竞态条件发生时,同一时刻只能有一个线程执行临界区代码
- 同步是由于线程执行的先后、顺序不同,需要一个线程等待其他线程运行到某个点
sysnchronized 语法:
synchronized (对象) {
// 临界区
}
使用 synchronized 解决上述的线程安全问题,代码:
static int num = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
num++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
num--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("num: {}", num);
}
反复执行均输出:
22:10:09.411 [main] DEBUG com.byteframework.learn.sync.Test2 - num: 0
总结:synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
使用面向对象改进锁对象
代码:
/**
* 锁对象面向对象改进
*/
@Slf4j
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("num: {}", room.getCounter());
}
}
/**
* 面向对象改进
*/
class Room {
private int counter = 0;
public void increment() {
synchronized (this) {
counter++;
}
}
public void decrement() {
synchronized (this) {
counter--;
}
}
public int getCounter(){
synchronized(this) {
return counter;
}
}
}
反复执行均输出:
21:12:10.888 [main] DEBUG com.byteframework.learn.sync.Test3 - num: 0
方法上的 synchronized
-
加在成员方法上
public class Test { public synchronized void test(){ } } // 等价于 public class Test { public void test(){ synchronized (this){ } } } -
加在静态方法上
public class Test { public synchronized static void test(){ } } // 等价于 public class Test { public void test(){ synchronized (Test.class){ } } }
总结:
synchronized 只能锁对象,加在成员方法上表示锁的是this对象或对象的实例,加在静态方法上表示锁的是类对象。
变量的线程安全分析
- 成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果他们被共享了,根据它们的状态是否能够改变,又分两种情况
- 如果只有读操作,则线程安全
- 如果有写操作,则这段代码是临界区,需要考虑线程安全
- 局部变量是否线程安全?
- 局部变量是线程安全的
- 但局部变量引用的对象则未必
- 如果该对象没有逃离方法的作用范围,它是线程安全的
- 如果该对象逃离方法的作用范围,需要考虑线程安全
举例1:(局部变量)
public static void test1(){
// 局部变量
int i = 10;
i++;
}
问:此静态方法是否存在线程安全问题?
答案:不存在
分析:i++ 虽然不是原子操作,但 i 变量不会被多个线程共享,所有不存在线程安全问题。
举例2:(成员变量)
public class TestThreadSafe {
static final int THREAD_NUM = 2;
static final int LOOP_NUM = 200;
public static void main(String[] args) {
ThreadUnsafe unsafe = new ThreadUnsafe();
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(() -> {
unsafe.method1(LOOP_NUM);
}, "thread_" + i).start();
}
}
}
class ThreadUnsafe {
// 成员变量
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNum) {
for (int i = 0; i < loopNum; i++) {
method2();
method3();
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
}
问:是否存在线程安全问题?
答案:存在
分析:多个线程操作的是同一个成员变量 list
运行输出:
Exception in thread "thread_0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:657)
at java.util.ArrayList.remove(ArrayList.java:496)
at com.byteframework.learn.sync.ThreadUnsafe.method3(TestThreadSafe.java:40)
at com.byteframework.learn.sync.ThreadUnsafe.method1(TestThreadSafe.java:31)
at com.byteframework.learn.sync.TestThreadSafe.lambda$main$0(TestThreadSafe.java:17)
at java.lang.Thread.run(Thread.java:748)
对代码进行改进:
public class TestThreadSafe {
static final int THREAD_NUM = 5;
static final int LOOP_NUM = 200;
public static void main(String[] args) {
ThreadSafe safe = new ThreadSafe();
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(() -> {
safe.method1(LOOP_NUM);
}, "thread_" + i).start();
}
}
}
class ThreadSafe {
public void method1(int loopNum) {
// 将成员变量修改为局部变量
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNum; i++) {
method2(list);
method3(list);
}
}
private void method2(ArrayList<String> list) {
list.add("1");
}
private void method3(ArrayList<String> list) {
list.remove(0);
}
}
反复执行再无错误输出。没有共享就没有线程安全问题
模拟售票例子
package com.byteframework.learn.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
/**
* 售票实例
*/
@Slf4j
public class ExerciseSell {
// 总票数20000张
final static int TICKET_COUNT = 20000;
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(TICKET_COUNT);
// 用来存储线程对象
List<Thread> threads = new ArrayList<>();
// 用来存储卖出去多少张票
List<Integer> amountList = new Vector<>();
// 模拟 200个买票用户的线程
for (int i = 0; i < 2000; i++) {
Thread thread = new Thread(() -> {
int count = ticketWindow.sell(randomAmount());
amountList.add(count);
}, "t1");
threads.add(thread);
thread.start();
}
// 等待所有线程结束
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 统计卖出的票数和剩余的票数
int sellCount = amountList.stream().mapToInt(i -> i).sum();
log.debug("总票数:{}", TICKET_COUNT);
log.debug("余票数:{}", ticketWindow.getCount());
log.debug("卖出的票数:{}", sellCount);
log.debug("余票数 + 卖出去的票数 = {}", ticketWindow.getCount() + sellCount);
}
// Random 为线程安全
static Random random = new Random();
// 随机数(1-5)
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
/**
* 售票窗口
*/
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
// 售票
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
多次执行后发现卖出去的票数加余票数不等于总票数:
22:14:13.222 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 总票数:20000
22:14:13.228 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 余票数:14074
22:14:13.228 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 卖出的票数:5930
22:14:13.228 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 余票数 + 卖出去的票数 = 20004
分析:售票方法中存在临界区
解决:给临界区加对象锁 synchronized, 锁实例对象。
调整后的代码:
package com.byteframework.learn.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
/**
* 售票实例
*/
@Slf4j
public class ExerciseSell {
final static int TICKET_COUNT = 20000;
public static void main(String[] args) {
// 总票数2000张
TicketWindow ticketWindow = new TicketWindow(TICKET_COUNT);
// 用来存储线程对象
List<Thread> threads = new ArrayList<>();
// 用来存储卖出去多少张票
List<Integer> amountList = new Vector<>();
// 模拟 200个买票用户的线程
for (int i = 0; i < 2000; i++) {
Thread thread = new Thread(() -> {
int count = ticketWindow.sell(randomAmount());
amountList.add(count);
}, "t1");
threads.add(thread);
thread.start();
}
// 等待所有线程结束
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 统计卖出的票数和剩余的票数
int sellCount = amountList.stream().mapToInt(i -> i).sum();
log.debug("总票数:{}", TICKET_COUNT);
log.debug("余票数:{}", ticketWindow.getCount());
log.debug("卖出的票数:{}", sellCount);
log.debug("余票数 + 卖出去的票数 = {}", ticketWindow.getCount() + sellCount);
}
// Random 为线程安全
static Random random = new Random();
// 随机数(1-5)
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
/**
* 售票窗口
*/
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
// 售票 (临界区代码加对象锁)
public synchronized int sell(int amount) {
// 临界区
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
反复执行后发现卖出的票数加余票数等于总票数:
22:21:53.545 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 总票数:20000
22:21:53.549 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 余票数:14017
22:21:53.549 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 卖出的票数:5983
22:21:53.549 [main] DEBUG com.byteframework.learn.sync.ExerciseSell - 余票数 + 卖出去的票数 = 20000
模拟转账例子
package com.byteframework.learn.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
/**
* 模拟转账例子
*/
@Slf4j
public class ExerciseTransfer {
// 账户A、B的金额
final static int A_MONEY = 10000;
final static int B_MONEY = 10000;
public static void main(String[] args) throws InterruptedException {
// 账户A、B
Account a = new Account(A_MONEY);
Account b = new Account(B_MONEY);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看多次转账后的总金额
log.debug("转账前, 两个账户的金额总和:{}", A_MONEY + B_MONEY);
log.debug("转账后, 两个账户的金额总和:{}", a.getMoney() + b.getMoney());
}
// Random 为线程安全
static Random random = new Random();
// 随机数(1-5)
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
/**
* 账户
*/
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
/**
* 转账
*
* @param target 目标账户
* @param amount 转账金额
*/
public void transfer(Account target, int amount) {
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
多次执行后总金额不一致:
22:45:01.328 [main] DEBUG com.byteframework.learn.sync.ExerciseTransfer - 转账前, 两个账户的金额总和:20000
22:45:01.332 [main] DEBUG com.byteframework.learn.sync.ExerciseTransfer - 转账后, 两个账户的金额总和:20136
分析:转账方法中存在临界区
解决:给临界区加对象锁 synchronized, 锁类对象。
调整后的代码:
package com.byteframework.learn.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
/**
* 模拟转账例子
*/
@Slf4j
public class ExerciseTransfer {
// 账户A、B的金额
final static int A_MONEY = 10000;
final static int B_MONEY = 10000;
public static void main(String[] args) throws InterruptedException {
// 账户A、B
Account a = new Account(A_MONEY);
Account b = new Account(B_MONEY);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
a.transfer(b, randomAmount());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 2000; i++) {
b.transfer(a, randomAmount());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看多次转账后的总金额
log.debug("转账前, 两个账户的金额总和:{}", A_MONEY + B_MONEY);
log.debug("转账后, 两个账户的金额总和:{}", a.getMoney() + b.getMoney());
}
// Random 为线程安全
static Random random = new Random();
// 随机数(1-5)
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
/**
* 账户
*/
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
/**
* 转账
*
* @param target 目标账户
* @param amount 转账金额
*/
public void transfer(Account target, int amount) {
// 锁实例对象,总金额还是不一致。
// synchronized(this){
// 锁类对象
synchronized (Account.class) {
if (this.money >= amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
}
反复执行后发现总金额一致:
22:50:58.301 [main] DEBUG com.byteframework.learn.sync.ExerciseTransfer - 转账前, 两个账户的金额总和:20000
22:50:58.307 [main] DEBUG com.byteframework.learn.sync.ExerciseTransfer - 转账后, 两个账户的金额总和:20000
wait notify
- obj.wait() 让进入 object 监视器的线程到 waitSet 中等待
- obj.notify() 在 objcet 上正在 waitSet 中等待的线程中挑一个唤醒
- obj.notifyAll() 让 objcet 上正在 waitSet 中等待的线程全部唤醒
它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法。
举例,不获得锁直接调用对象的 wait方法:
static final Object lock = new Object();
public static void main(String[] args) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
出现异常:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.byteframework.learn.sync.TestWait.main(TestWait.java:15)
所以,必须要先获取到对象的锁后,才能调用对象的 wait、notify、notifyAll 方法。 代码调整为:
static final Object lock = new Object();
public static void main(String[] args) {
synchronized(lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试 wait notify notifyAll, 例子:
/**
* 测试 wait notify notifyAll
*/
@Slf4j
public class TestWaitNotify {
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
log.debug("线程t1获取到锁后进入waitSet...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("线程t1开始执行后续代码...");
}
}, "t1").start();
new Thread(() -> {
synchronized (lock) {
log.debug("线程t2获取到锁后进入waitSet...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("线程t2开始执行后续代码...");
}
}, "t2").start();
synchronized (lock) {
log.debug("主线程获取到锁后调用totify, 挑一个唤醒...");
lock.notify();
// log.debug("主线程获取到锁后调用totifyAll, 全部唤醒...");
// lock.notifyAll();
}
}
}
执行notify时,输出:
22:31:10.935 [t1] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t1获取到锁后进入waitSet...
22:31:10.938 [t2] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t2获取到锁后进入waitSet...
22:31:10.938 [main] DEBUG com.byteframework.learn.sync.TestWaitNotify - 主线程获取到锁后调用totify, 挑一个唤醒...
22:31:10.938 [t1] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t1开始执行后续代码...
执行 notifyAll 时, 输出:
22:33:16.187 [t1] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t1获取到锁后进入waitSet...
22:33:16.192 [t2] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t2获取到锁后进入waitSet...
22:33:16.192 [main] DEBUG com.byteframework.learn.sync.TestWaitNotify - 主线程获取到锁后调用totifyAll, 全部唤醒...
22:33:16.192 [t2] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t2开始执行后续代码...
22:33:16.192 [t1] DEBUG com.byteframework.learn.sync.TestWaitNotify - 线程t1开始执行后续代码...
sleep 和 wait 的区别
不同点:
- sleep 是 Thread 的方法, 而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用, 但 wait 需要和 synchronized 一起使用
- sleep 睡眠的同时,不会释放对象锁, 但 wait 在等待的时候会释放对象锁
相同点:
- 都会让出 CPU 的时间片
- 他们的状态都是 TIMED_WAITING
举例:
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized(lock) {
log.debug("线程t1获得锁...");
try {
//log.debug("线程t1调用wait...");
//lock.wait(10000);
TimeUnit.SECONDS.sleep(10);
log.debug("sleep 10 秒后...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
synchronized(lock){
log.debug("主线程获得锁...");
}
}
调用sleep 后的输出:
21:38:41.168 [t1] DEBUG com.byteframework.learn.sync.TestSleepWait - 线程t1获得锁...
21:38:51.185 [t1] DEBUG com.byteframework.learn.sync.TestSleepWait - sleep 10 秒后...
21:38:51.185 [main] DEBUG com.byteframework.learn.sync.TestSleepWait - 主线程获得锁...
可以看出 sleep 后线程没有释放对象锁, sleep 的时间到后主线程才获得了对象锁.
调用 wait 后的输出:
21:46:56.362 [t1] DEBUG com.byteframework.learn.sync.TestSleepWait - 线程t1获得锁...
21:46:56.367 [t1] DEBUG com.byteframework.learn.sync.TestSleepWait - 线程t1调用wait...
21:46:57.362 [main] DEBUG com.byteframework.learn.sync.TestSleepWait - 主线程获得锁...
可以看出 t1 线程获取到对象锁后, 调用 wait 方法又释放了对象锁, 1秒后主线程获得了对象锁.
wait notify 的正确姿势
step1
package com.byteframework.learn.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TestStep1 {
static final Object room = new Object();
// 有烟没烟
static boolean hasCigarette = false;
static boolean hasTakeout;
public static void main(String[] args) throws InterruptedException {
// 小南没烟不干活
new Thread(()-> {
synchronized(room) {
log.debug("有烟没? [{}]", hasCigarette);
if(!hasCigarette) {
log.debug("没烟, 先休息一会...");
try {
TimeUnit.SECONDS.sleep(5);
log.debug("5秒后...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没? [{}]", hasCigarette);
if(hasCigarette) {
log.debug("有烟, 开始干活...");
}
}
}, "小南").start();
for(int i = 0; i< 5; i++){
new Thread(() -> {
synchronized(room) {
log.debug("开始干活...");
}
}, "其他人[" + i + "]").start();
}
TimeUnit.SECONDS.sleep(1);
new Thread(()-> {
hasCigarette = true;
log.debug("1秒后, 烟到了...");
}, "送烟的").start();
}
}
输出:
22:18:23.314 [小南] DEBUG com.byteframework.learn.sync.TestStep1 - 有烟没? [false]
22:18:23.318 [小南] DEBUG com.byteframework.learn.sync.TestStep1 - 没烟, 先休息一会...
22:18:24.325 [送烟的] DEBUG com.byteframework.learn.sync.TestStep1 - 1秒后, 烟到了...
22:18:28.318 [小南] DEBUG com.byteframework.learn.sync.TestStep1 - 5秒后...
22:18:28.318 [小南] DEBUG com.byteframework.learn.sync.TestStep1 - 有烟没? [true]
22:18:28.318 [小南] DEBUG com.byteframework.learn.sync.TestStep1 - 有烟, 开始干活...
22:18:28.318 [其他人[4]] DEBUG com.byteframework.learn.sync.TestStep1 - 开始干活...
22:18:28.319 [其他人[3]] DEBUG com.byteframework.learn.sync.TestStep1 - 开始干活...
22:18:28.319 [其他人[2]] DEBUG com.byteframework.learn.sync.TestStep1 - 开始干活...
22:18:28.319 [其他人[0]] DEBUG com.byteframework.learn.sync.TestStep1 - 开始干活...
22:18:28.319 [其他人[1]] DEBUG com.byteframework.learn.sync.TestStep1 - 开始干活...
分析:
-
其他干活的线程, 都要一直阻塞, 效率太低.
-
小南线程必须睡足了5秒才会醒来, 就算烟提前到了, 也无法立即醒来.
解决方法: 使用 wait - notify 机制
step2
优化 step1代码:
package com.byteframework.learn.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TestStep2 {
static final Object room = new Object();
// 有烟没烟
static boolean hasCigarette = false;
static boolean hasTakeout;
public static void main(String[] args) throws InterruptedException {
// 小南没烟不干活
new Thread(()-> {
synchronized(room) {
log.debug("有烟没? [{}]", hasCigarette);
if(!hasCigarette) {
log.debug("没烟, 先休息一会...");
try {
// 使用 wait 释放对象锁
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没? [{}]", hasCigarette);
if(hasCigarette) {
log.debug("有烟, 开始干活...");
}
}
}, "小南").start();
for(int i = 0; i< 5; i++){
new Thread(() -> {
synchronized(room) {
log.debug("开始干活...");
}
}, "其他人[" + i + "]").start();
}
TimeUnit.SECONDS.sleep(1);
new Thread(()-> {
synchronized(room) {
hasCigarette = true;
log.debug("1秒后, 烟到了...");
room.notify();
}
}, "送烟的").start();
}
}
输出:
22:27:29.834 [小南] DEBUG com.byteframework.learn.sync.TestStep2 - 有烟没? [false]
22:27:29.839 [小南] DEBUG com.byteframework.learn.sync.TestStep2 - 没烟, 先休息一会...
22:27:29.839 [其他人[4]] DEBUG com.byteframework.learn.sync.TestStep2 - 开始干活...
22:27:29.839 [其他人[3]] DEBUG com.byteframework.learn.sync.TestStep2 - 开始干活...
22:27:29.839 [其他人[2]] DEBUG com.byteframework.learn.sync.TestStep2 - 开始干活...
22:27:29.840 [其他人[1]] DEBUG com.byteframework.learn.sync.TestStep2 - 开始干活...
22:27:29.840 [其他人[0]] DEBUG com.byteframework.learn.sync.TestStep2 - 开始干活...
22:27:30.840 [送烟的] DEBUG com.byteframework.learn.sync.TestStep2 - 1秒后, 烟到了...
22:27:30.840 [小南] DEBUG com.byteframework.learn.sync.TestStep2 - 有烟没? [true]
22:27:30.840 [小南] DEBUG com.byteframework.learn.sync.TestStep2 - 有烟, 开始干活...
分析:
可以看到, 小南线程在休息的期间释放了对象锁, 这是其他线程可以获取对象锁进行"干活". 烟送到后使用了 notify 通知了正在休息的小南, 小南立即起来干活.
思考: (虚假唤醒)
如果此时有两个线程都在等待 (比如: 小南没烟不干活, 小女没吃的不干活), notify 是否能准确唤醒小南或小女
step3
虚假唤醒:
浙公网安备 33010602011771号