1.共享带来的问题

临界区 Critical Section

  • 一个程序运行多个线程本身是没有问题的
  • 问题出在多个线程访问共享资源
    • 多个线程读共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

例如,下面代码中的临界区

  1. static int counter = 0;
  2. static void increment()
  3. // 临界区
  4. {
  5. counter++; }
  6. static void decrement()
  7. // 临界区
  8. {
  9. counter--; }

竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

synchronized 解决方案

应用之互斥
为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

本次使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一 时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

注意
虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

Monitor 概念(监视器)

描述

Monitor其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象,

  1. Object o = new Object();
  2. new Thread(() -> {
  3. synchronized (o)
  4. {
  5. }
  6. },"t1").start();

Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。
image.png

Java 对象头

以 32 位虚拟机为例

普通对象:

|———————————————————————————————|
|Object Header (64 bits) |
|——————————————————|————————————-|
|Mark Word (32 bits) |Klass Word (32 bits) |
|——————————————————|————————————-|

数组对象:

|————————————————————————————————————————-|
|Object Header (96 bits) |
|————————————————|———————————-|————————————|
|Mark Word(32bits) |Klass Word(32bits) |array length(32bits) |
|————————————————|———————————-|————————————|

其中 Mark Word 结构为

|———————————————————————————-|——————————|
|Mark Word (32 bits) |State|
|———————————————————————————-|——————————|
|hashcode:25| age:4 | biased_lock:0 | 01|Normal|
|———————————————————————————-|——————————|
|thread:23 | epoch:2 | age:4 | biased_lock:1 | 01|Biased|
|———————————————————————————-|——————————|
|ptr_to_lock_record:30| 00| Lightweight Locked |
|———————————————————————————-|——————————|
|ptr_to_heavyweight_monitor:30| 10| Heavyweight Locked |
|———————————————————————————-|——————————|
|| 11|Marked for GC|
|———————————————————————————-|——————————|

64 位虚拟机 Mark Word

|——————————————————————————————————|——————————|
|Mark Word (64 bits) |State|
|——————————————————————————————————|——————————|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01|Normal|
|——————————————————————————————————|——————————|
| thread:54 | epoch:2| unused:1 | age:4 | biased_lock:1 | 01|Biased|
|——————————————————————————————————|——————————|
|ptr_to_lock_record:62| 00| Lightweight Locked |
|——————————————————————————————————|——————————|
|ptr_to_heavyweight_monitor:62| 10| Heavyweight Locked |
|——————————————————————————————————|——————————|
|| 11|Marked for GC|
|——————————————————————————————————|——————————|

wait notify

API 介绍

  • obj.wait() 让进入 object 监视器的线程到 waitSet 等待
  • obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

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

  1. final static Object obj = new Object();
  2. public static void main(String[] args) {
  3. new Thread(() -> {
  4. synchronized (obj) {
  5. log.debug("执行....");
  6. try {
  7. obj.wait(); // 让线程在obj上一直等待下去
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. log.debug("其它代码....");
  12. }
  13. }).start();
  14. new Thread(() -> {
  15. synchronized (obj) {
  16. log.debug("执行....");
  17. try {
  18. obj.wait(); // 让线程在obj上一直等待下去
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. log.debug("其它代码....");
  23. }
  24. }).start();
  25. // 主线程两秒后执行
  26. sleep(2);
  27. log.debug("唤醒 obj 上其它线程");
  28. synchronized (obj) {
  29. obj.notify(); // 唤醒obj上一个线程
  30. // obj.notifyAll(); // 唤醒obj上所有等待线程
  31. }
  32. }

notify 的一种结果

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

notifyAll 的结果

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

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

wait notify 的正确姿势

sleep(long n) 和 wait(long n) 的区别

1) sleep 是 Thread 方法,而 wait 是 Object 的方法
2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
4) 它们 状态 TIMED_WAITING

step 1

  1. static final Object room = new Object();
  2. static boolean hasCigarette = false;
  3. static boolean hasTakeout = false;