4.6 wait和notify

建议先看看wait和notify方法的javadoc文档

API 介绍

obj.wait() 让进入 object 监视器的线程到 waitSet 等待

obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒 ,随机唤醒一个线程

obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法

final static Object obj = new Object();
public static void main(String[] args) {new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}}).start();new Thread(() -> {synchronized (obj) {log.debug("执行....");try {obj.wait(); // 让线程在obj上一直等待下去} catch (InterruptedException e) {e.printStackTrace();}log.debug("其它代码....");}}).start();// 主线程两秒后执行sleep(2);log.debug("唤醒 obj 上其它线程");synchronized (obj) {obj.notify(); // 唤醒obj上一个线程// obj.notifyAll(); // 唤醒obj上所有等待线程}
}

notify 的一种结果

20:00:53.096 [Thread-0] c.TestWaitNotify - 执行....
20:00:53.099 [Thread-1] c.TestWaitNotify - 执行....
20:00:55.096 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
20:00:55.096 [Thread-0] c.TestWaitNotify - 其它代码.... 

notifyAll 的结果

19:58:15.457 [Thread-0] c.TestWaitNotify - 执行....
19:58:15.460 [Thread-1] c.TestWaitNotify - 执行....
19:58:17.456 [main] c.TestWaitNotify - 唤醒 obj 上其它线程
19:58:17.456 [Thread-1] c.TestWaitNotify - 其它代码....
19:58:17.456 [Thread-0] c.TestWaitNotify - 其它代码....

wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就有机会获取对象的锁。无限制等待,直到 notify 为止, wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify

wait和notify原理

友情提示:waiting表示的是已经获得锁但是放弃锁(对应的就是wait),Blocked是希望能获取锁(对应sleep)

wait和notify正确使用姿势

友情提示:这里多次提到虚假唤醒,当唤醒多个线程中,有部分是无用功,这些无用功的唤醒的就是虚假唤醒。

代码1:

package cn.itcast.n4;import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {static final Object room = new Object();static boolean hasCigarette = false; // 有没有烟static boolean hasTakeout = false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会!");sleep(2);}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}}}, "小南").start();for (int i = 0; i < 5; i++) {new Thread(() -> {synchronized (room) {log.debug("可以开始干活了");}}, "其它人").start();}sleep(1);new Thread(() -> {// 这里能不能加 synchronized (room)?
//            synchronized (room) {hasCigarette = true;log.debug("烟到了噢!");
//            }}, "送烟的").start();}}

输出结果

代码2:

友情提示:相比上面的代码,这里在送烟的方法中加入了synchronized这是有问题的,这会导致送烟的时候获取不到锁,必须要先让小南sleep两秒才能释放锁,这样小南就没有干活。

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {static final Object room = new Object();static boolean hasCigarette = false; // 有没有烟static boolean hasTakeout = false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会!");sleep(2);}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");}}}, "小南").start();for (int i = 0; i < 5; i++) {new Thread(() -> {synchronized (room) {log.debug("可以开始干活了");}}, "其它人").start();}sleep(1);new Thread(() -> {// 这里能不能加 synchronized (room)?synchronized (room) {hasCigarette = true;log.debug("烟到了噢!");}}, "送烟的").start();}}

结果:

发现小南没有干活

说明问题:使用sleep解决向下运行的缺点,后面的其他人都必须等待小南完成之后才能干活导致效率低下。

针对上面的问题使用wait和notify

代码

package cn.itcast.n4;import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {static final Object room = new Object();static boolean hasCigarette = false;static boolean hasTakeout = false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会!");try {room.wait(2000);} 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("可以开始干活了");}}, "其它人").start();}sleep(1);new Thread(() -> {synchronized (room) {hasCigarette = true;log.debug("烟到了噢!");room.notify();}}, "送烟的").start();}}

运行结果

使用notifyAll唤醒所有阻塞的线程

package cn.itcast.n4;import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep3 {static final Object room = new Object();static boolean hasCigarette = false;static boolean hasTakeout = false;// 虚假唤醒public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);if (!hasCigarette) {log.debug("没烟,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");} else {log.debug("没干成活...");}}}, "小南").start();new Thread(() -> {synchronized (room) {Thread thread = Thread.currentThread();log.debug("外卖送到没?[{}]", hasTakeout);if (!hasTakeout) {log.debug("没外卖,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖送到没?[{}]", hasTakeout);if (hasTakeout) {log.debug("可以开始干活了");} else {log.debug("没干成活...");}}}, "小女").start();sleep(1);new Thread(() -> {synchronized (room) {hasTakeout = true;log.debug("外卖到了噢!");room.notifyAll();}}, "送外卖的").start();}}

结果

注意,这里只是唤醒了小女,但是虚假唤醒了小南。

 解决上面问题

将if改成while

package cn.itcast.n4;import lombok.extern.slf4j.Slf4j;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep4 {static final Object room = new Object();static boolean hasCigarette = false;static boolean hasTakeout = false;public static void main(String[] args) {new Thread(() -> {synchronized (room) {log.debug("有烟没?[{}]", hasCigarette);while (!hasCigarette) {log.debug("没烟,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有烟没?[{}]", hasCigarette);if (hasCigarette) {log.debug("可以开始干活了");} else {log.debug("没干成活...");}}}, "小南").start();new Thread(() -> {synchronized (room) {Thread thread = Thread.currentThread();log.debug("外卖送到没?[{}]", hasTakeout);if (!hasTakeout) {log.debug("没外卖,先歇会!");try {room.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("外卖送到没?[{}]", hasTakeout);if (hasTakeout) {log.debug("可以开始干活了");} else {log.debug("没干成活...");}}}, "小女").start();sleep(1);new Thread(() -> {synchronized (room) {hasTakeout = true;log.debug("外卖到了噢!");room.notifyAll();}}, "送外卖的").start();}}

运行结果

4.6.1同步模式之保护性暂停(线程设计模式)

友情提示:保护性暂停是一个线程等待另一个线程的结果,而join是一个线程等待另一个线程的结束

即 Guarded Suspension,用在一个线程等待另一个线程的执行结果,要点:

  1. 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
  2. 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
  3. JDK 中,join 的实现、Future 的实现,采用的就是此模式
  4. 因为要等待另一方的结果,因此归类到同步模式

代码:Test22.java Test23.java这是带超时时间的

友情提示:下面的设置了超时时间。


// 增加超时效果
class GuardedObject {// 标识 Guarded Objectprivate int id;public GuardedObject(int id) {this.id = id;}public int getId() {return id;}// 结果private Object response;// 获取结果// timeout 表示要等待多久 2000public Object get(long timeout) {synchronized (this) {// 开始时间 15:00:00long begin = System.currentTimeMillis();// 经历的时间long passedTime = 0;while (response == null) {// 这一轮循环应该等待的时间long waitTime = timeout - passedTime;// 经历的时间超过了最大等待时间时,退出循环if (timeout - passedTime <= 0) {break;}try {this.wait(waitTime); // 虚假唤醒 15:00:01} catch (InterruptedException e) {e.printStackTrace();}// 求得经历时间passedTime = System.currentTimeMillis() - begin; // 15:00:02  1s}return response;}}// 产生结果public void complete(Object response) {synchronized (this) {// 给结果成员变量赋值this.response = response;this.notifyAll();}}
}

 测试一:正常情况

运行结果

测试二:设置了虚假唤醒,传递的对象为空。

运行结果

 测试三:超过了过期时间

运行结果

Join的实现原理

Test23.java中jiang'dao'de关于超时的增强,在join(long millis) 的源码中得到了体现:

 public final synchronized void join(long millis)throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {// join一个指定的时间while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}}

同步模式之保护性暂停(扩展)

友情提示:这种方式就是RPC框架解决解耦结果产生者和结果消费者,这种保护性暂停的线程设计模式是生产者和消费者是一一对应的。

多任务版 GuardedObject图中 Futures 就好比居民楼一层的信箱(每个信箱有房间编号),左侧的 t0,t2,t4 就好比等待邮件的居民,右侧的 t1,t3,t5 就好比邮递员如果需要在多个类之间使用 GuardedObject 对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。和生产者消费者模式的区别就是:这个生产者和消费者之间是一一对应的关系,但是生产者消费者模式并不是。rpc框架的调用中就使用到了这种模式。 Test24.java

代码:解耦等待与生产

友情提示:Mailboxes和GuardedObject两个是有一定的复用性的。

package cn.itcast.test;import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;import java.util.Hashtable;
import java.util.Map;
import java.util.Set;@Slf4j(topic = "c.Test20")
public class Test20 {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 3; i++) {new People().start();}Sleeper.sleep(1);for (Integer id : Mailboxes.getIds()) {new Postman(id, "内容" + id).start();}}
}@Slf4j(topic = "c.People")
class People extends Thread{@Overridepublic void run() {// 收信GuardedObject guardedObject = Mailboxes.createGuardedObject();log.debug("开始收信 id:{}", guardedObject.getId());Object mail = guardedObject.get(5000);log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);}
}@Slf4j(topic = "c.Postman")
class Postman extends Thread {private int id;private String mail;public Postman(int id, String mail) {this.id = id;this.mail = mail;}@Overridepublic void run() {GuardedObject guardedObject = Mailboxes.getGuardedObject(id);log.debug("送信 id:{}, 内容:{}", id, mail);guardedObject.complete(mail);}
}class Mailboxes {private static Map<Integer, GuardedObject> boxes = new Hashtable<>();private static int id = 1;// 产生唯一 idprivate static synchronized int generateId() {return id++;}public static GuardedObject getGuardedObject(int id) {return boxes.remove(id);}public static GuardedObject createGuardedObject() {GuardedObject go = new GuardedObject(generateId());boxes.put(go.getId(), go);return go;}public static Set<Integer> getIds() {return boxes.keySet();}
}// 增加超时效果
class GuardedObject {// 标识 Guarded Objectprivate int id;public GuardedObject(int id) {this.id = id;}public int getId() {return id;}// 结果private Object response;// 获取结果// timeout 表示要等待多久 2000public Object get(long timeout) {synchronized (this) {// 开始时间 15:00:00long begin = System.currentTimeMillis();// 经历的时间long passedTime = 0;while (response == null) {// 这一轮循环应该等待的时间long waitTime = timeout - passedTime;// 经历的时间超过了最大等待时间时,退出循环if (timeout - passedTime <= 0) {break;}try {this.wait(waitTime); // 虚假唤醒 15:00:01} catch (InterruptedException e) {e.printStackTrace();}// 求得经历时间passedTime = System.currentTimeMillis() - begin; // 15:00:02  1s}return response;}}// 产生结果public void complete(Object response) {synchronized (this) {// 给结果成员变量赋值this.response = response;this.notifyAll();}}
}

运行结果

4.6.2异步模式之生产者/消费者(线程设计模式)

友情提示:这种生产者、消费之线程的设计模式是生产者和消费者是一对多的,该模式用于jdk阻塞队列,相比保护性暂停设计模式是一一对应的。

要点

  1. 与前面的保护性暂停中的 GuardObject 不同,不需要产生结果和消费结果的线程一一对应
  2. 消费队列可以用来平衡生产和消费的线程资源
  3. 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
  4. 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
  5. JDK 中各种阻塞队列,采用的就是这种模式

“异步”的意思就是生产者产生消息之后消息没有被立刻消费,而“同步模式”中,消息在产生之后被立刻消费了。

我们写一个线程间通信的消息队列,要注意区别,像rabbit mq等消息框架是进程间通信的。

代码:

package cn.itcast.test;import lombok.extern.slf4j.Slf4j;import java.util.LinkedList;import static cn.itcast.n2.util.Sleeper.sleep;@Slf4j(topic = "c.Test21")
public class Test21 {public static void main(String[] args) {MessageQueue queue = new MessageQueue(2);for (int i = 0; i < 3; i++) {int id = i;new Thread(() -> {queue.put(new Message(id , "值"+id));}, "生产者" + i).start();}new Thread(() -> {while(true) {sleep(1);Message message = queue.take();}}, "消费者").start();}}// 消息队列类 , java 线程之间通信
@Slf4j(topic = "c.MessageQueue")
class MessageQueue {// 消息的队列集合private LinkedList<Message> list = new LinkedList<>();// 队列容量private int capcity;public MessageQueue(int capcity) {this.capcity = capcity;}// 获取消息public Message take() {// 检查队列是否为空synchronized (list) {while(list.isEmpty()) {try {log.debug("队列为空, 消费者线程等待");list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 从队列头部获取消息并返回Message message = list.removeFirst();log.debug("已消费消息 {}", message);list.notifyAll();return message;}}// 存入消息public void put(Message message) {synchronized (list) {// 检查对象是否已满while(list.size() == capcity) {try {log.debug("队列已满, 生产者线程等待");list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 将消息加入队列尾部list.addLast(message);log.debug("已生产消息 {}", message);list.notifyAll();}}
}final class Message {private int id;private Object value;public Message(int id, Object value) {this.id = id;this.value = value;}public int getId() {return id;}public Object getValue() {return value;}@Overridepublic String toString() {return "Message{" +"id=" + id +", value=" + value +'}';}
}

运行结果:

4.7 park & unpack

4.7.1 基本使用

它们是 LockSupport 类中的方法 Test26.java

// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark;

代码:

先 park 再 unpark

@Slf4j(topic = "c.TestParkUnpark")
public class TestParkUnpark {public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("start...");sleep(2);log.debug("park...");LockSupport.park();log.debug("resume...");}, "t1");t1.start();sleep(1);log.debug("unpark...");LockSupport.unpark(t1);}
}

运行结果

 先 unpark 再 park

Thread t1 = new Thread(() -> {log.debug("start...");sleep(2);log.debug("park...");LockSupport.park();log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);

运行结果

18:43:50.765 c.TestParkUnpark [t1] - start...
18:43:51.764 c.TestParkUnpark [main] - unpark...
18:43:52.769 c.TestParkUnpark [t1] - park...
18:43:52.769 c.TestParkUnpark [t1] - resume...

4.7.2 park unpark 原理

每个线程都有自己的一个 Parker 对象(这部分是底层C语言实现的),由三部分组成 _counter, _cond和 _mutex

  1. 打个比喻线程就像一个旅人,Parker 就像他随身携带的背包,条件变量 _ cond就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
  2. 调用 park 就是要看需不需要停下来歇息
    1. 如果备用干粮耗尽,那么钻进帐篷歇息
    2. 如果备用干粮充足,那么不需停留,继续前进
  3. 调用 unpark,就好比令干粮充足
    1. 如果这时线程还在帐篷,就唤醒让他继续前进
    2. 如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进
      1. 因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

可以不看例子,直接看实现过程

先调用park再调用unpark的过程

1.先调用park

  1. 当前线程调用 Unsafe.park() 方法
  2. 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁(mutex对象有个等待队列 _cond)
  3. 线程进入 _cond 条件变量阻塞
  4. 设置 _counter = 0

2.调用unpark

  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 唤醒 _cond 条件变量中的 Thread_0
  3. Thread_0 恢复运行
  4. 设置 _counter 为 0

先调用unpark再调用park的过程

  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 当前线程调用 Unsafe.park() 方法
  3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
  4. 设置 _counter 为 0

4.8 线程状态转换

  1. RUNNABLE <--> WAITING

    1. 线程用synchronized(obj)获取了对象锁后

      1. 调用obj.wait()方法时,t 线程从RUNNABLE --> WAITING
      2. 调用obj.notify(),obj.notifyAll(),t.interrupt()时
        1. 竞争锁成功,t 线程从WAITING --> RUNNABLE
        2. 竞争锁失败,t 线程从WAITING --> BLOCKED
  2. RUNNABLE <--> WAITING

    1. 当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING
    2. 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE
  3. RUNNABLE <--> WAITING

    1. 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING 注意是当前线程在t 线程对象的监视器上等待
    2. t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE
  4. RUNNABLE <--> TIMED_WAITING

    t 线程用 synchronized(obj) 获取了对象锁后

    1. 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING
    2. t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
      1. 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE
      2. 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED
  5. RUNNABLE <--> TIMED_WAITING

    1. 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING 注意是当前线程在t 线程对象的监视器上等待
    2. 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING --> RUNNABLE
  6. RUNNABLE <--> TIMED_WAITING

    1. 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
    2. 当前线程等待时间超过了 n 毫秒或调用了线程 的 interrupt() ,当前线程从 TIMED_WAITING --> RUNNABLE
  7. RUNNABLE <--> TIMED_WAITING

    1. 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE --> TIMED_WAITING
    2. 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING--> RUNNABLE

4.9 多把锁

多把不相干的锁

一间大屋子有两个功能:睡觉、学习,互不相干。 现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低 解决方法是准备多个房间(多个对象锁)

友情提示:主要解决的是并发度高的思想,如果把类作为锁,并发度会变低,如果按功能加锁,并发度会有所提升。

代码:

class BigRoom {public void sleep() {synchronized (this) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (this) {log.debug("study 1 小时");Sleeper.sleep(1);}}
}

执行

BigRoom bigRoom = new BigRoom();
new Thread(() -> {bigRoom.compute();
},"小南").start();
new Thread(() -> {bigRoom.sleep();
},"小女").start();

执行结果

12:13:54.471 [小南] c.BigRoom - study 1 小时
12:13:55.476 [小女] c.BigRoom - sleeping 2 小时

改进

class BigRoom {private final Object studyRoom = new Object();private final Object bedRoom = new Object();public void sleep() {
synchronized (bedRoom) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (studyRoom) {log.debug("study 1 小时");Sleeper.sleep(1);}}
}

执行结果

12:15:35.069 [小南] c.BigRoom - study 1 小时
12:15:35.069 [小女] c.BigRoom - sleeping 2 小时

将锁的粒度细分

好处,是可以增强并发度

坏处,如果一个线程需要同时获得多把锁,就容易发生死锁

4.10 活跃性

友情提示:线程的死锁,饥饿可以用ReentrantLock解决,活锁只需要将两个线程代码交错开即可
线程的安全性描述的是程序正确的运行,而活跃性描述的是应该运行的程序一定会运行,如果某些代码造成了后面应该运行的代码一直运行不到,那就是活跃性问题。
比如代码中有无限循环,后面的代码就一直执行不到,就造成了活跃性问题。

线程的活跃性问题主要有三种:死锁、饥饿、活锁。下面会依次提到。

4.10.1 死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁t1 线程获得A对象锁,接下来想获取B对象的锁;t2 线程获得B对象锁,接下来想获取A对象的锁例。

代码:

Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {synchronized (A) {log.debug("lock A");sleep(1);synchronized (B) {log.debug("lock B");log.debug("操作...");}}
}, "t1");
Thread t2 = new Thread(() -> {synchronized (B) {log.debug("lock B");sleep(0.5);synchronized (A) {log.debug("lock A");
log.debug("操作...");}}
}, "t2");
t1.start();
t2.start();

运行结果

12:22:06.962 [t2] c.TestDeadLock - lock B
12:22:06.962 [t1] c.TestDeadLock - lock A 

4.10.2 定位死锁

检测死锁可以使用 jconsole工具;或者使用 jps 定位进程 id,再用 jstack 定位死锁:Test28.java

下面使用jstack工具进行演示

D:\我的项目\JavaLearing\java并发编程\jdk8>jps
1156 RemoteMavenServer36
20452 Test25
9156 Launcher
23544 Jps
23848
22748 Test28D:\我的项目\JavaLearing\java并发编程\jdk8>jstack 22748
2020-07-12 18:54:44
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.211-b12 mixed mode):"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002a03800 nid=0x5944 waiting on condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE//................省略了大部分内容.............//
Found one Java-level deadlock:
=============================
"线程二":waiting to lock monitor 0x0000000002afc0e8 (object 0x00000000db9f76d0, a java.lang.Object),which is held by "线程1"
"线程1":waiting to lock monitor 0x0000000002afe1e8 (object 0x00000000db9f76e0, a java.lang.Object),which is held by "线程二"Java stack information for the threads listed above:
===================================================
"线程二":at com.concurrent.test.Test28.lambda$main$1(Test28.java:39)- waiting to lock <0x00000000db9f76d0> (a java.lang.Object)- locked <0x00000000db9f76e0> (a java.lang.Object)at com.concurrent.test.Test28$$Lambda$2/326549596.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
"线程1":at com.concurrent.test.Test28.lambda$main$0(Test28.java:23)- waiting to lock <0x00000000db9f76e0> (a java.lang.Object)- locked <0x00000000db9f76d0> (a java.lang.Object)at com.concurrent.test.Test28$$Lambda$1/1343441044.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)

使用jconsle

可以观察到t1和t2死锁了

4.10.3 哲学家就餐问题

有五位哲学家,围坐在圆桌旁。 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。 如果筷子被身边的人拿着,自己就得等待

当每个哲学家即线程持有一根筷子时,他们都在等待另一个线程释放锁,因此造成了死锁。这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情 况

代码:

筷子类

class Chopstick {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}

哲学家类

class Philosopher extends Thread {Chopstick left;Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}private void eat() {log.debug("eating...");Sleeper.sleep(1);}@Overridepublic void run() {while (true) {// 获得左手筷子synchronized (left) {// 获得右手筷子synchronized (right) {// 吃饭eat();}// 放下右手筷子}// 放下左手筷子}}
}

就餐

Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();

运行结果

12:33:15.575 [苏格拉底] c.Philosopher - eating...
12:33:15.575 [亚里士多德] c.Philosopher - eating...
12:33:16.580 [阿基米德] c.Philosopher - eating...
12:33:17.580 [阿基米德] c.Philosopher - eating...
// 卡在这里, 不向下运行

使用 jconsole 检测死锁,发现

-------------------------------------------------------------------------
名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)- 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)- 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)- 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)- 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)- 已锁定 cn.itcast.Chopstick@7f31245a (筷子4)

这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情 况

4.10.4 活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

public class TestLiveLock {static volatile int count = 10;
static final Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 期望减到 0 退出循环while (count > 0) {sleep(0.2);count--;log.debug("count: {}", count);}}, "t1").start();new Thread(() -> {// 期望超过 20 退出循环while (count < 20) {sleep(0.2);count++;log.debug("count: {}", count);}}, "t2").start();}
}

运行结果:

不断的在11和12之间徘徊

友情提示:死锁和活锁的理解,死锁是线程之间各自持有对方的锁,导致对方不能释放锁。活锁是线程之间能够相互修改对方的条件,导致程序一直执行,不断的徘徊。活锁是没有占有对方的锁。

4.10.5 饥饿

很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题下面我讲一下一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题,就是两个线程对两个不同的对象加锁的时候都使用相同的顺序进行加锁。 但是会产生饥饿问题

顺序加锁的解决方案

4.11 ReentrantLock

ReentrantLock比 synchronized好的优点:

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁(先进先出)
  4. 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待,也就是比synchronized相比有多个waitset,而synchronized只有一个

与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {// 临界区
} finally {// 释放锁reentrantLock.unlock();
}

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

友情提示:说的直白点,就是在外层方法上加锁了,内部方法同样加了锁,线程是可以执行内部方法的。如果不可重入,线程就会阻塞在外面进入不了内部方法。

代码

static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {method1();
}
public static void method1() {lock.lock();try {log.debug("execute method1");method2();} finally {lock.unlock();}
}
public static void method2() {lock.lock();try {log.debug("execute method2");method3();} finally {lock.unlock();}
}
public static void method3() {lock.lock();try {log.debug("execute method3");} finally {lock.unlock();}
}

输出

17:59:11.862 [main] c.TestReentrant - execute method1
17:59:11.865 [main] c.TestReentrant - execute method2
17:59:11.865 [main] c.TestReentrant - execute method3

可打断

可以被其他线程打断当前阻塞的线程,synchronized和lock.lock()方法是不可以打断的但是可以使用lock.lockInterruptibly();当处于阻塞状态可以被其线程打断。

下面的是在主线程中加入锁,由于主线程没有释放锁,导致t1线程进入阻塞状态,随后在主线程中对t1进行打断,就可以执行t1线程代码。

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");try {lock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();log.debug("等锁的过程中被打断");return;}try {log.debug("获得了锁");} finally {lock.unlock();}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {sleep(1);t1.interrupt();log.debug("执行打断");
} finally {lock.unlock();
}

输出结果

18:02:40.520 [main] c.TestInterrupt - 获得了锁
18:02:40.524 [t1] c.TestInterrupt - 启动...
18:02:41.530 [main] c.TestInterrupt - 执行打断
java.lang.InterruptedException at
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchr
onizer.java:898) at
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchron
izer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at cn.itcast.n4.reentrant.TestInterrupt.lambda$main$0(TestInterrupt.java:17) at java.lang.Thread.run(Thread.java:748)
18:02:41.532 [t1] c.TestInterrupt - 等锁的过程中被打断

注意如果是不可中断模式如lock.lock()方法,那么即使用了 interrupt 也不会让等待中断

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {log.debug("启动...");
lock.lock();try {log.debug("获得了锁");} finally {lock.unlock();}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {sleep(1);t1.interrupt();log.debug("执行打断");sleep(1);
} finally {log.debug("释放了锁");lock.unlock();
}

输出

18:06:56.261 [main] c.TestInterrupt - 获得了锁
18:06:56.265 [t1] c.TestInterrupt - 启动...
18:06:57.266 [main] c.TestInterrupt - 执行打断 // 这时 t1 并没有被真正打断, 而是仍继续等待锁
18:06:58.267 [main] c.TestInterrupt - 释放了锁
18:06:58.267 [t1] c.TestInterrupt - 获得了锁

锁超时

友情提示:使用锁超时解决哲学家就餐死锁问题

下面的是立刻失败

友情提示:lock.tryLock(),表示的是立刻执行,返回的是布尔值,true就是获得锁,false就是没有获得锁

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {log.debug("启动...");if (!lock.tryLock()) {log.debug("获取立刻失败,返回");return;}try {log.debug("获得了锁");} finally {lock.unlock();}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {sleep(2);
} finally {lock.unlock();
}

输出

18:15:02.918 [main] c.TestTimeout - 获得了锁
18:15:02.921 [t1] c.TestTimeout - 启动...
18:15:02.921 [t1] c.TestTimeout - 获取立刻失败,返回

下面的是超时失败

友情提示:lock.tryLock(1, TimeUnit.SECONDS),设置过期时间,也就是超时时间。返回的是布尔值,true就是获得锁,false就是没有获得锁

ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {log.debug("启动...");try {if (!lock.tryLock(1, TimeUnit.SECONDS)) {log.debug("获取等待 1s 后失败,返回");return;}} catch (InterruptedException e) {e.printStackTrace();}try {log.debug("获得了锁");} finally {lock.unlock();}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {sleep(2);
} finally {lock.unlock();
}

输出结果

18:19:40.537 [main] c.TestTimeout - 获得了锁
18:19:40.544 [t1] c.TestTimeout - 启动...
18:19:41.547 [t1] c.TestTimeout - 获取等待 1s 后失败,返回

使用 tryLock 解决哲学家就餐问题

class Chopstick extends ReentrantLock {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}
class Philosopher extends Thread {Chopstick left;Chopstick right;public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}@Overridepublic void run() {while (true) {// 尝试获得左手筷子if (left.tryLock()) {try {// 尝试获得右手筷子if (right.tryLock()) {try {eat();} finally {right.unlock();}}} finally {left.unlock();}}}}private void eat() {log.debug("eating...");Sleeper.sleep(1);}
}

公平锁

synchronized锁中,在entrylist等待的锁在竞争时不是按照先到先得来获取锁的,所以说synchronized锁是不公平的;ReentranLock锁默认是不公平的,但是可以通过设置实现公平锁。本意是为了解决之前提到的饥饿问题,但是公平锁一般没有必要,会降低并发度,使用trylock也可以实现。公平锁,其实就是先进入的先获得锁。

可以查看 ReentranLock的构造方法,默认是false,只需要将其改成true就可以变成公平锁

 ReentrantLock 默认是不公平的

ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 500; i++) {new Thread(() -> {lock.lock();try {System.out.println(Thread.currentThread().getName() + " running...");} finally {lock.unlock();}}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {System.out.println(Thread.currentThread().getName() + " start...");lock.lock();try {System.out.println(Thread.currentThread().getName() + " running...");} finally {lock.unlock();}
}, "强行插入").start();
lock.unlock();

强行插入,有机会在中间输出

注意:该实验不一定总能复现

t39 running...
t40 running...
t41 running...
t42 running...
t43 running...
强行插入 start...
强行插入 running...
t44 running...
t45 running...
t46 running...
t47 running...
t49 running... 

改为公平锁后

ReentrantLock lock = new ReentrantLock(true);

强行插入,总是在最后输出

t465 running...
t464 running...
t477 running...
t442 running...
t468 running...
t493 running...
t482 running...
t485 running...
t481 running...
强行插入 running... 

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待 ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  1. synchronized 是那些不满足条件的线程都在一间休息室等消息
  2. 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤 醒

使用要点: Test34.java

  1. await 前需要获得锁
  2. await 执行后,会释放锁,进入 conditionObject 等待
  3. await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,执行唤醒的线程爷必须先获得锁
  4. 竞争 lock 锁成功后,从 await 后继续执行
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {new Thread(() -> {try {lock.lock();while (!hasCigrette) {
try {waitCigaretteQueue.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("等到了它的烟");} finally {lock.unlock();}}).start();new Thread(() -> {try {lock.lock();while (!hasBreakfast) {try {waitbreakfastQueue.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("等到了它的早餐");} finally {lock.unlock();}}).start();sleep(1);sendBreakfast();sleep(1);sendCigarette();
}
private static void sendCigarette() {lock.lock();try {log.debug("送烟来了");hasCigrette = true;waitCigaretteQueue.signal();} finally {lock.unlock();}
}
private static void sendBreakfast() {lock.lock();try {log.debug("送早餐来了");hasBreakfast = true;waitbreakfastQueue.signal();} finally {lock.unlock();
}
}

输出:

18:52:27.680 [main] c.TestCondition - 送早餐来了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送烟来了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的烟

同步模式之顺序控制(线程设计模式)

  1. 固定运行顺序,比如,必须先 2 后 1 打印

wait notify 版

package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test25")
public class Test25 {static final Object lock = new Object();// 表示 t2 是否运行过static boolean t2runned = false;public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (lock) {while (!t2runned) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("1");}}, "t1");Thread t2 = new Thread(() -> {synchronized (lock) {log.debug("2");t2runned = true;lock.notify();}}, "t2");t1.start();t2.start();}
}

Park Unpark版本

package cn.itcast.test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.LockSupport;@Slf4j(topic = "c.Test26")
public class Test26 {public static void main(String[] args) {Thread t1 = new Thread(() -> {LockSupport.park();log.debug("1");}, "t1");t1.start();new Thread(() -> {log.debug("2");LockSupport.unpark(t1);},"t2").start();}
}
  1. 交替输出,线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

wait notify 版

package cn.itcast.test;import lombok.extern.slf4j.Slf4j;@Slf4j(topic = "c.Test27")
public class Test27 {public static void main(String[] args) {WaitNotify wn = new WaitNotify(1, 5);new Thread(() -> {wn.print("a", 1, 2);}).start();new Thread(() -> {wn.print("b", 2, 3);}).start();new Thread(() -> {wn.print("c", 3, 1);}).start();}
}/*
输出内容       等待标记     下一个标记a           1             2b           2             3c           3             1*/
class WaitNotify {// 打印               a           1             2public void print(String str, int waitFlag, int nextFlag) {for (int i = 0; i < loopNumber; i++) {synchronized (this) {while(flag != waitFlag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(str);flag = nextFlag;this.notifyAll();}}}// 等待标记private int flag; // 2// 循环次数private int loopNumber;public WaitNotify(int flag, int loopNumber) {this.flag = flag;this.loopNumber = loopNumber;}
}

Lock 条件变量版 

package cn.itcast.test;import lombok.extern.slf4j.Slf4j;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Test28")
public class Test28 {public static void main(String[] args) {AwaitSignal2 as = new AwaitSignal2(3);as.start(new Thread(() -> {as.print("a");}), new Thread(() -> {as.print("b");}), new Thread(() -> {as.print("c");}), new Thread(() -> {as.print("d");}));}
}@Slf4j(topic = "c.AwaitSignal")
class AwaitSignal2 extends ReentrantLock {private Map<Thread, Condition[]> map = new HashMap<>();public void start(Thread... threads) {Condition[] temp = new Condition[threads.length];for (int i = 0; i < threads.length; i++) {temp[i] = this.newCondition();}for (int i = 0; i < threads.length; i++) {Condition current = temp[i];Condition next;if (i == threads.length - 1) {next = temp[0];} else {next = temp[i + 1];}map.put(threads[i], new Condition[]{current, next});}for (Thread thread : map.keySet()) {thread.start();}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}this.lock();try {map.get(threads[0])[0].signal();} finally {this.unlock();}}public void print(String str) {for (int i = 0; i < loopNumber; i++) {this.lock();try {Condition[] conditions = map.get(Thread.currentThread());conditions[0].await();log.debug(str);conditions[1].signal();} catch (InterruptedException e) {e.printStackTrace();} finally {this.unlock();}}}// 循环次数private int loopNumber;public AwaitSignal2(int loopNumber) {this.loopNumber = loopNumber;}
}

 Park Unpark 版

package cn.itcast.test;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.LockSupport;@Slf4j(topic = "c.Test31")
public class Test31 {static Thread t1;static Thread t2;static Thread t3;public static void main(String[] args) {ParkUnpark pu = new ParkUnpark(5);t1 = new Thread(() -> {pu.print("a", t2);});t2 = new Thread(() -> {pu.print("b", t3);});t3 = new Thread(() -> {pu.print("c", t1);});t1.start();t2.start();t3.start();LockSupport.unpark(t1);}
}class ParkUnpark {public void print(String str, Thread next) {for (int i = 0; i < loopNumber; i++) {LockSupport.park();System.out.print(str);LockSupport.unpark(next);}}private int loopNumber;public ParkUnpark(int loopNumber) {this.loopNumber = loopNumber;}
}

本章小结

本章我们需要重点掌握的是

  1. 分析多线程访问共享资源时,哪些代码片段属于临界区
  2. 使用 synchronized 互斥解决临界区的线程安全问题
    1. 掌握 synchronized 锁对象语法
    2. 掌握 synchronzied 加载成员方法和静态方法语法
    3. 掌握 wait/notify 同步方法
  3. 使用 lock 互斥解决临界区的线程安全问题 掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
  4. 学会分析变量的线程安全性、掌握常见线程安全类的使用
  5. 了解线程活跃性问题:死锁、活锁、饥饿
  6. 应用方面
    1. 互斥:使用 synchronized 或 Lock 达到共享资源互斥效果,实现原子性效果,保证线程安全。
    2. 同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果。
  7. 原理方面
    1. monitor、synchronized 、wait/notify 原理
    2. synchronized 进阶原理
    3. park & unpark 原理
  8. 模式方面
    1. 同步模式之保护性暂停
    2. 异步模式之生产者消费者
    3. 同步模式之顺序控制

JUC共享模型之管程阶段四相关推荐

  1. JUC笔记-共享模型之管程 (Monitor)

    JUC-共享模型之管程( Monitor) 一.线程安全问题(重点) 1.1 同步 1.2 线程出现问题的根本原因分析 1.3 synchronized 解决方案 1.3.1 同步代码块 1.3.2 ...

  2. Java 多线程共享模型之管程(下)

    共享模型之管程 wait.notify wait.notify 原理 Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态 BLOCKED 和 W ...

  3. 并发编程(三)---共享模型之管程

    三.共享模型之管程 3.1临界区(Critical Section) 一个程序运行多个线程本身是没有问题的 问题出在多个线程访问共享资源 多个线程读取共享资源其实也没有问题 在多个线程对共享资源读写操 ...

  4. 【并发编程】(学习笔记-共享模型之管程)-part3

    文章目录 并发编程-共享模型之管程-3 1.共享带来的问题 1-1 临界区 Critical Section 1-2 竞态条件 Race Condition 2.synchronized 解决方案 2 ...

  5. Java并发编程 - 共享模型之管程

    一. 共享带来的问题 小故事 老王(操作系统)有一个功能强大的算盘(CPU),现在想把它租出去,赚一点外快 小南.小女(线程)来使用这个算盘来进行一些计算,并按照时间给老王支付费用 但小南不能一天 2 ...

  6. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread -> Activity、主线程阶段 二 )

    文章目录 前言 一.ActivityThread 类 handleLaunchActivity -> performLaunchActivity 方法 二.Instrumentation.new ...

  7. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread -> Activity、主线程阶段 一 )

    文章目录 前言 一.ClientTransactionHandler.scheduleTransaction 二.ActivityThread.H 处理 EXECUTE_TRANSACTION 消息 ...

  8. (80)FPGA建立时间与保持时间及时序模型-面试必问(四)(第16天)

    (80)FPGA建立时间与保持时间及时序模型-面试必问(四)(第16天) 1 文章目录 1)文章目录 2)FPGA初级课程介绍 3)FPGA初级课程架构 4)FPGA建立时间与保持时间及时序模型-面试 ...

  9. 利用 SGA 共享池,避开 parse 阶段

    同一功能同一性能不同写法 SQL 的影响 如一个 SQL 在 A 程序员写的为 Select * from zl_yhjbqk B 程序员写的为 Select * from dlyx.zl_yhjbq ...

最新文章

  1. python for-Python for循环及基础用法详解
  2. leetcode算法题--二维区域和检索 - 矩阵不可变
  3. WebDriver元素等待机制
  4. 造谣“外卖员因差评杀人” 女子被依法刑事拘留
  5. python3导入ssl报错_python3中pip3安装出错,找不到SSL的解决方式
  6. linux查文件被哪些程序占用—fuser和lsof的使用
  7. linux bash学习(一)
  8. 计算机win7卡顿如何解决方法,win7系统运行卡顿的解决方法
  9. css权威指南学习笔记
  10. 搭建环境方便简单教程之php环境详细搭建
  11. java毕业设计(论文)答辩提纲,毕业论文答辩提纲模板.doc
  12. 74HC573锁存器
  13. Turtle 画正方形螺旋线
  14. 域名可以修改绑定的服务器么,域名备案绑定的服务器可以改么
  15. 联盟:微信封号最新规则以及解决方法
  16. PS动作把人物照片变成炭笔素描画效果
  17. BLAM源码解析(三)—— 定时器总揽大局
  18. Windows 7 bluetooth 外围设备 解决方案
  19. 1.2.2 musl pwn
  20. springboot毕设项目影评网站系统4i684(java+VUE+Mybatis+Maven+Mysql)

热门文章

  1. Use Whoosh——Whoosh入门
  2. php 将十进制转换为二进制,php十进制转二进制不用函数
  3. 谈谈从Windows 和 Unix 的发展看待现代操作系统的跌宕起伏
  4. amis笔记及遇到的问题解决方案
  5. java中封装的好处_【Java基础】java封装的好处
  6. 使用Serv-U搭建FTP服务器
  7. FFMPEG+SDL2 实现播放器功能
  8. 人生修煉電影篇之-------------------- 《狗十三》
  9. 2.3大数据存储技术
  10. 模仿京东登录页面——小程序