Java并发编程基础--4共享模型之管程

4 共享模型之管程

注:本系列仅供个人学习使用,原视频:https://www.bilibili.com/video/BV16J411h7Rd?p=49

4.1 共享带来的问题

问题

上下文切换与指令交错导致共享资源访问的读写问题。

临界区 Critical Section

一段代码如果存在对共享资源的多线程读写操作,称这段代码为临界区。

竞争条件Race Condition

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

4.2 synchronized解决方案

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

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量
synchronized(对象) // 线程1, 线程2(blocked)
{
 临界区
}

面向对象的改进

class Room {
 int value = 0;
 public void increment() {
 synchronized (this) {
 value++;
 }
 }
 public void decrement() {
 synchronized (this) {
 value--;
 }
 }
 public int get() {
 synchronized (this) {
 return value;
 }
 }
}

线程八锁

主要判断锁的对象是否相同(当前对象、类对象)

4.3 方法上添加synchronized

对象锁与方法锁相比,对象锁的效率更高。synchronized也可以加载方法上,此时默认锁的是this当前对象。synchronized俗称对象锁。

4.4 变量的线程安全分析

成员变量和静态变量

  • 如果没有共享则线程安全
  • 如果线程共享了
    • 都是读操作,线程安全(待定)
    • 有读写,线程不安全

局部变量是否安全

  • 局部变量是安全的(每个线程都有自己专属的栈区)
  • 局部变量引用未必
    • 局部引用没有逃离方法去,线程安全
    • 局部引用逃离方法区,线程不安全

常见的线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent包下的类

这里的线程安全是指,多个线程调用它同一个实例的某个方法时是线程安全的。

Hashtable table = new Hashtable();
new Thread(()->{
 table.put("key", "value1");
}).start();
new Thread(()->{
 table.put("key", "value2");
}).start();
  • 它们的每个方法是原子的
  • 但注意它们多个方法的组合不是原子的
Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
 table.put("key", value);
}

不可变类型线程安全

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的。

4.5 习题

public class ExerciseSell {
 public static void main(String[] args) {
 TicketWindow ticketWindow = new TicketWindow(2000);
 List<Thread> list = new ArrayList<>();
 // 用来存储买出去多少张票
 List<Integer> sellCount = new Vector<>();
 for (int i = 0; i < 2000; i++) {
 Thread t = new Thread(() -> {
 // 分析这里的竞态条件
 int count = ticketWindow.sell(randomAmount());
 sellCount.add(count);
 });
 list.add(t);
 t.start();
 }
 list.forEach((t) -> {
 try {
 t.join();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 });
 // 买出去的票求和
 log.debug("selled count:{}",sellCount.stream().mapToInt(c -> c).sum());
 // 剩余票数
 log.debug("remainder count:{}", ticketWindow.getCount());
 }
 // 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;
 }
 }
}

另外,用下面的代码行不行,为什么?

List<Integer> sellCount = new ArrayList<>();

测试脚本

for /L %n in (1,1,10) do java -cp ".;C:\Users\manyh\.m2\repository\ch\qos\logback\logbackclassic\1.2.3\logback-classic-1.2.3.jar;C:\Users\manyh\.m2\repository\ch\qos\logback\logbackcore\1.2.3\logback-core-1.2.3.jar;C:\Users\manyh\.m2\repository\org\slf4j\slf4japi\1.7.25\slf4j-api-1.7.25.jar" cn.itcast.n4.exercise.ExerciseSell
转账练习
public class ExerciseTransfer {
 public static void main(String[] args) throws InterruptedException {
 Account a = new Account(1000);
 Account b = new Account(1000);
 Thread t1 = new Thread(() -> {
 for (int i = 0; i < 1000; i++) {
 a.transfer(b, randomAmount());
 }
 }, "t1");
 Thread t2 = new Thread(() -> {
 for (int i = 0; i < 1000; i++) {
 b.transfer(a, randomAmount());
 }
 }, "t2");
 t1.start();
 t2.start();
 t1.join();
 t2.join();
      // 查看转账2000次后的总金额
 log.debug("total:{}",(a.getMoney() + b.getMoney()));
 }
 // Random 为线程安全
 static Random random = new Random();
 // 随机 1~100
 public static int randomAmount() {
 return random.nextInt(100) +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;
 }
 public void transfer(Account target, int amount) {
 if (this.money > amount) {
 this.setMoney(this.getMoney() - amount);
 target.setMoney(target.getMoney() + amount);
 }
 }
}

这样改正行不行,为什么?

public synchronized void transfer(Account target, int amount) {
 if (this.money > amount) {
 this.setMoney(this.getMoney() - amount);
 target.setMoney(target.getMoney() + amount);
 }
}

4.6 Monitor概念

Java对象头

以32位虚拟机为例

普通对象

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

数组对象(多了32位数组长度)

|---------------------------------------------------------------------------------|
|                              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 |
|--------------------------------------------------------------------|--------------------|

参考资料:https://stackoverflow.com/questions/26357186/what-is-in-java-object-header

Monitor(锁)

Monitor被反以为监视器管程

每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置为指向Monitor对象的指针。Monitor结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdt1rAqA-1592232545022)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527110414279.png)]

  • obj没有被锁的情况,当Thread-1执行synchronized(obj)时,JVM会将obj关联到操作系统的一个Monitor,并在对象头中的Mark Word里面记录Monitor的指针。此时Monitor会将Owner指向该对象所在线程。
  • 如果此时有另一个线程Thread-2执行synchronized(obj),由于阻塞,Monitor会将新的线程添加到EntryList中。
  • 当Thread-1执行完毕之后,Monitor会将Owner置null,并唤醒EntryList,选择一个线程作为Owner所指。

注意:

  • synchronized必须室进入同一对象的monitor才有上述效果
  • 不加synchronized的对象不会关联监视器,不遵从上述规则

*原理之synchronized

public class Synchronized1 {
    static final Object lock = new Object();
    static int counter = 0;
    public static void main(String[] args) {
        synchronized(lock){
            counter++;
        }
    }
}

查看字节码

javap -c Synchronized1.class

输出:main函数对应的字节码

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field lock:Ljava/lang/Object; lock引用
       3: dup								// 复制引用
       4: astore_1							// lock引用暂存到 slot 1
       5: monitorenter						// 在lock对象的MarkWord中写入Monitor的指针
       6: getstatic     #13                 // Field counter:I,获取静态变量counter
       9: iconst_1							//准备常量1
      10: iadd								// +1
      11: putstatic     #13                 // Field counter:I,将结果保存在counter里面
      14: aload_1							// 获取lock对象引用
      15: monitorexit						// 将lock对象MarkWord重置,唤醒EntryList	
      16: goto          24					// 异常处理(异常发生时,将锁释放)
      19: astore_2							// 将捕获的异常进行暂存
      20: aload_1							// 加载lock引用
      21: monitorexit						// 对lock对象的MarkWord进行还原
      22: aload_2							// 加载暂存的异常
      23: athrow							// 抛出异常
      24: return							// 返回
    Exception table:						// 异常监视表,from...to...表示监视的代码行数
       from    to  target type
           6    16    19   any
          19    22    19   any

*原理之synchronized进阶

轻量级锁

一个对象虽然有多线程访问,但是多线程访问时间是错开的。也就是每次都会分配Monitor对象,这样做会消耗系统资源。此时可以使用轻量级锁来优化。

轻量级锁对使用者来说是透明的,即语法依然是synchronized。轻量锁操作过程如下。

假设有两个方法同步同一个对象,利用同一个对象锁。

static final Object obj = new Object();
public static void method1(){
    synchronized(obj){
        // 同步块 A
        method2()
    }
}
public static void method2(){
    synchronized(obj){
        // 同步块 B
    }
}
  • 每个线程都有独立地栈帧当方法一对obj加锁时,会在线程栈中创建一个锁记录对象(Lock Record)。内部可以存储锁定对象的Mark Word与引用。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yp8euJOK-1592232545028)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527114301832.png)]

  • 如果cas(compare-and-swap)成功,对象头中存储了锁记录地址和状态00,图示如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0d4poeC-1592232545031)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527114454740.png)]

1)锁重入

cas失败情况1:同个线程内有多个栈帧对同一对象加锁

这种情况是锁重入,那么在添加一条Lock Record作为重入的计数。

此时没有发生Mark Word替换,主要由以下原因:对象中已经存储了对一个对其加轻量锁的Lock Record对象,交换会发生覆盖,导致错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HR7WbGxz-1592232545034)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527114927882.png)]

  • 当退出synchronized代码块时,锁记录值不为null,则使用cas将Mark WOrd恢复给对象。
    • 成功,则解锁成功。
    • 失败说明轻量锁进行了锁膨胀,或者已经升级为重量锁,进入重量锁解锁过程。
2)锁膨胀

cas失败情况2:另外一个线程对同一个对象加锁。如果在尝试加轻量级锁的过程中,CAS操作无法成功,这一情况为其他线程为此对象加上了轻量级锁,这是需要进行锁膨胀,将轻量级锁升级为重量级锁。

  • 当Thread-1尝试给对象加锁时,发现Thread-0已经加过轻量级锁了。这时进入锁膨胀流程:
    • 即为obj对象添加Monitor锁。
    • 首先申请Monitor锁。
    • 然后更改对象的Mark Word。将其指向Monitor
    • 最后将自己添加到Monitor的EntryList中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfFpCxgy-1592232545037)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527141250203.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HarUwCrk-1592232545038)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527141702823.png)]

  • 当Thread-0退出同步块解锁时,使用cas将Mark Word回复给对象头,失败。这时会进入重量级解锁流程。即按照Monitor地址找到Monitor对象,将Owner设置为null,最后唤醒EntryList中的BLOCKED线程。
3)自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果自旋成功(把持锁的线程已经退出了同步,释放了锁),这时,当前线程就可以避免阻塞。

具体做法类似于设置while true循环,访问若干次。

  • 自旋锁会占用CPU时间,单CPU就是浪费,多核自旋才能发挥优势。
  • Java 6 之后自旋锁是自适应的,如果第一次自旋成功,那么会认为自旋成功的可能性比较高,否则就少自旋,甚至不自旋
偏向锁

轻量级锁在没有竞争的时候,每次重入仍需执行CAS操作。

Java6 中引入了偏向锁来做进一步优化:只有第一次使用CAS江县城ID设置到对象的Mark Word头,之后发现这个线程ID就是自己的,就表示没有竞争,不用CAS。已有只要不发生竞争,这个对象酒柜线程所拥有。

例如,下列程序仅使用单个线程。

static final Object obj = new Object();
public static void m1() {
 synchronized( obj ) {
 // 同步块 A
 m2();
 }
}
public static void m2() {
 synchronized( obj ) {
 // 同步块 B
 m3();
 }
}
public static void m3() {
 synchronized( obj ) {
      // 同步块 C
 }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mCqTPXiP-1592232545040)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527143514218.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W5rpUQJA-1592232545042)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527143525604.png)]

偏向状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HTiEMARp-1592232545044)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200527144349939.png)]

|--------------------------------------------------------------------|--------------------|
| 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 |
|--------------------------------------------------------------------|--------------------|
测试关闭偏向锁
  1. 添加VM参数(本身程序就应用于高并发的场景)
-XX:-UseBiasedLocking
  1. 调用hashcode(),可以屏蔽掉偏向锁。

    • 轻量级锁会在锁记录中记录 hashCode
    • 重量级锁会在 Monitor 中记录 hashCode
  2. 有多个线程适用对象

    private static void test2() throws InterruptedException {
     Dog d = new Dog();
     Thread t1 = new Thread(() -> {
     synchronized (d) {
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     synchronized (TestBiased.class) {
     TestBiased.class.notify();
     }
     // 如果不用 wait/notify 使用 join 必须打开下面的注释
     // 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的
     /*try {
     System.in.read();
     } catch (IOException e) {
     e.printStackTrace();
     }*/
     }, "t1");
     t1.start();
     Thread t2 = new Thread(() -> {
     synchronized (TestBiased.class) {
     try {
     TestBiased.class.wait();
     } catch (InterruptedException e) {
     e.printStackTrace();
     }
     }
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     synchronized (d) {
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }
     log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
     }, "t2");
     t2.start();
    }
    

    第二个线程加锁过程中会升级成轻量级锁,释放后变为未偏向的状态。

    [t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
    [t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
    [t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 
    [t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
    
  3. 使用wait/notify

    • 因为wait/notify重量级锁才有
public static void main(String[] args) throws InterruptedException {
 Dog d = new Dog();
 Thread t1 = new Thread(() -> {
 log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
 synchronized (d) {
 log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
 try {
 d.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
 }
 }, "t1");
 t1.start();
 new Thread(() -> {
 try {
 Thread.sleep(6000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 synchronized (d) {
 log.debug("notify");
 d.notify();
 }
 }, "t2").start();
}
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101 
[t2] - notify 
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
批量重偏向

虽然对象被多个线程访问,但是没有竞争,这是偏向线程T1的对象仍有机会偏向T2,重偏向会重置对象的Thread ID。

当撤销偏向锁阈值超过20次时,jvm会判定偏向错了,于是会在给这些对象加锁时重新偏向至加锁线程。

例子:对一个list内的对象进行加偏向锁,从第20次开始,后续的对象被被重新加了Thread-2的偏向锁。

private static void test3() throws InterruptedException {
 Vector<Dog> list = new Vector<>();
 Thread t1 = new Thread(() -> {
 for (int i = 0; i < 30; i++) {
 Dog d = new Dog();
 list.add(d);
 synchronized (d) {
 log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 }
 }
 synchronized (list) {
 list.notify();
 } 
 }, "t1");
 t1.start();
 
 Thread t2 = new Thread(() -> {
 synchronized (list) {
 try {
 list.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 log.debug("===============> ");
 for (int i = 0; i < 30; i++) {
 Dog d = list.get(i);
 log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 synchronized (d) {
 log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 }
 log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 }
 }, "t2");
 t2.start();
}
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - ===============> 
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
撤销偏向

当撤销偏向锁超过40次后,jvm会觉得偏向错了,不应该偏向,于是整个类的对象都会变成不可偏向的,新建的对象也是不可偏向的。

package test;

import java.util.Vector;
import java.util.concurrent.locks.LockSupport;

public class BiasBatchCancel {
    static Thread t1, t2, t3;

    private static void test4() throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        int loopNumber = 39;
        t1 = new Thread(() -> {
            for (int i = 0; i < loopNumber; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
            }
            LockSupport.unpark(t2);
        }, "t1");
        t1.start();
        t2 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();
        t3 = new Thread(() -> {
            LockSupport.park();
            log.debug("===============> ");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }, "t3");
        t3.start();
        t3.join();
        log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
    }
}
锁消除

Java运行时有JIT即时编译器,会对热点代码进行优化。比如当没有多线程时,会将synchronized优化掉

@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
 static int x = 0;
 @Benchmark
 public void a() throws Exception {
 x++;
 }
 @Benchmark
 public void b() throws Exception {
 Object o = new Object();
 synchronized (o) {
 x++;
    }
 }
}

java -jar benchmarks.jar

锁优化,运行速度相近

Benchmark Mode Samples Score Score error Units 
c.i.MyBenchmark.a avgt 5 1.542 0.056 ns/op 
c.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op

java -XX:-EliminateLocks -jar benchmarks.jar

去除锁优化,运行变慢

Benchmark Mode Samples Score Score error Units 
c.i.MyBenchmark.a avgt 5 1.507 0.108 ns/op 
c.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op
锁粗化

4.7 wait notify

API介绍

  • obj.wait()让object监视器的线程到waitSet等待
  • wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify
  • obj.notify()从obj关联的monitor中的waitSet随机挑选一个线程将其唤醒
  • obj.notifyAll()唤醒obj关联的monitor中的waitSet中的所有线程

wait/notify与Thread.sleep的区别

  1. sleep属于Thread的静态方法,而wait是Object的方法
  2. sleep不需要强制和synchronized配合使用,但wait需要
  3. sleep在睡眠时不会释放对象锁,但在wait时会释放

sleep与wait的状态都是TIMED_WAITING

正确使用的姿势

synchronized(lock){
    while(条件不成立){
        lock.wait();
    }
    //干活
}
// 另一个线程
synchronized(lock){
    lock.notifyAll();
}

4.9 park与unpark

java.util.concurrent.locks.LockSupport

LockSupport 提供的是一个许可,如果存在许可,线程在调用park的时候,会立马返回,此时许可也会被消费掉,如果没有许可,则会阻塞。调用unpark的时候,如果许可本身不可用,则会使得许可可用。许可只有一个不可累加。park的许可通过原子变量_count实现,当被消耗时,_count为0,只要拥有许可,就会立即返回。

每个线程都有自己的一个 Parker 对象。

基本使用1

// 暂停当前线程
LockSupport.park();
// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)
Thread t1 = new Thread(() -> {
 log.debug("start...");
 sleep(1);
 log.debug("park...");
 LockSupport.park();
 log.debug("resume...");
},"t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);

基本使用2: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);

与wait…notify的区别

  • wait, notify, notifyAll必须配合Object Monitor一起使用,而park与unpark却不必。
  • park与unpark以线程为单位进行阻塞和唤醒,唤醒时比notify与notifyAll要精确。
  • 可以在park前调用unpark,而不能在wait前线调用notify

*原理

应用场景

park与unpark适用于producer快而consumer慢的场景。

有一点比较难理解的,是unpark操作可以再park操作之前。也就是说,先提供许可。当某线程调用park时,已经有许可了,它就消费这个许可,然后可以继续运行。这其实是必须的。考虑最简单的生产者(Producer)消费者(Consumer)模型:Consumer需要消费一个资源,于是调用park操作等待;Producer则生产资源,然后调用unpark给予Consumer使用的许可。非常有可能的一种情况是,Producer先生产,这时候Consumer可能还没有构造好(比如线程还没启动,或者还没切换到该线程)。那么等Consumer准备好要消费时,显然这时候资源已经生产好了,可以直接用,那么park操作当然可以直接运行下去。如果没有这个语义,那将非常难以操作。

参考链接:参考1. 参考2

4.10 重新理解线程状态转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzHmFYUS-1592232545047)(C:\Users\dnm\AppData\Roaming\Typora\typora-user-images\image-20200615152854139.png)]

情况 1 New–>RUNNABLE

当调用t.start()方法时发生

情况 2 RUNNABLE<–>WAITING

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

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

情况 3 RUNNABLE <–> WAITING

  • 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING

    • 注意是当前线程在t 线程对象的监视器上等待
  • t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE

情况 4 RUNNABLE <–> WAITING

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

情况 5 RUNNABLE <–> TIMED_WAITING

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

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

情况 6 RUNNABLE <–> TIMED_WAITING

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

情况 7 RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
  • 当前线程等待时间超过了 n 毫秒,当前线程从TIMED_WAITING --> RUNNABLE

情况 8 RUNNABLE <–> TIMED_WAITING

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

情况 9 RUNNABLE <–> BLOCKED

  • t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED
  • 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED 情况 10 RUNNABLE <–> TERMINATED 当前线程所有代码运行完毕,进入 TERMINATED

情况 10 RUNNABLE <–> TERMINATED

当前线程所有代码运行完毕,进入 TERMINATED

4.11多个不相干锁

将锁粒度细分:

- 增强并发度
- 容易发生死锁

4.12 活跃性

死锁

多个线程需要获得多个锁(有竞争关系),容易造成死锁

死锁定位
  • jps找到JVM进程 id,然后使用jstack定位死锁
  • jconsole
哲学家就餐

有五位哲学家,围坐在圆桌旁。

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

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

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();
 }
}
饥饿

一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束。

4.13 ReentrantLock

相较于synchronized,ReetrantanceLock具有以下特点

  • 可中断
  • 可以设置超时等待
  • 可以设置为公平锁
  • 支持多个条件变量

基本语法

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

可重入

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

可打断
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
    try {
        // 如果没有竞争,那么机会获取Lock对象
        // 如果有竞争就会进入阻塞队列,可以被其他线程用interrupt打断
        lock.lockInterruptibly();
        log.debug("t1获得到了锁");
    }catch (InterruptedException e){
        e.printStackTrace();
        log.debug("在等待过程中被打断");
    }
}, "t1");

lock.lock();
log.debug("获得了锁");
t1.start();
try {
    Thread.sleep(1);
    t1.interrupt();

    log.debug("执行打断");
    Thread.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
    log.debug("释放了锁");
}
锁超时TryLock
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
    log.debug("启动...");
//            if (!lock.tryLock()) {
//                log.debug("获取立刻失败,返回");
//                // 没有获得锁,立即返回
//                return;
//            }
    try {
        if (!lock.tryLock(2, TimeUnit.SECONDS)) {
            log.debug("延时获得锁失败,返回");
            return;
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        log.debug("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
    Thread.sleep(2);
    // t1可以被打断
//            t1.interrupt();
} finally {
    lock.unlock();
}
公平锁

ReentranLock默认是不公平的,可以通过调用重载方法构造公平锁。

ReentrantLock lock = new ReentrantLock(false);

公平锁一般没有必要,会降低并发度

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待。

ReentrantLock条件变量的强大之处在于,他可以设置多个条件变量

  • synchronized将所有不满足条件的线程都放在一个队列里面
  • 而ReentrantLock支持多条件变量,可以设置更细粒度的等待队列

使用要点:

  • await需要获得锁
  • await执行后会释放
  • await的线程被唤醒后(打断、超时)会重新竞争Lock锁
  • 竞争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;
	
    try {
        waitCigaretteQueue.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
	waitbreakfastQueue.signal();

* 同步之条件变量

三种方法实现顺序输出

wait notify
public class Test27_Control_Thread_Order_WAN {
    public static void main(String[] args) {
        WaitNotify wn = new WaitNotify(1, 5);
        Thread t1 = new Thread(() -> {
            wn.print("a", 1, 2);
        });
        Thread t2 = new Thread(() -> {
            wn.print("b", 2, 3);
        });
        Thread t3 = new Thread(() -> {
            wn.print("c", 3, 1);
        });

        t1.start();
        t2.start();
        t3.start();
    }
}
/*
输出内容 等待标记 下一个标记
a          1    2
b          2    3
c          3    1
 */
class WaitNotify{
    // 等待标记
    private int flag;
    private int loopNumber;

    public 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.println(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
    WaitNotify(int f, int ln){
        flag = f;
        loopNumber = ln;
    }
}
ReentrantLock
public class Test27_Control_Thread_Order_ReentrantLock2 {
    public static void main(String[] args) {
        AwaitSignal as = new AwaitSignal(5);
        Condition c1 = as.newCondition();
        Condition c2 = as.newCondition();
        Condition c3 = as.newCondition();
        new Thread(() -> {
           as.print("a", c1, c2);
        }).start();
        new Thread(() -> {
           as.print("b", c2, c3);
        }).start();
        new Thread(() -> {
           as.print("c", c3, c1);
        }).start();

        as.start(c1);
    }
}

@Slf4j
class AwaitSignal extends ReentrantLock{
    private int loopNumber;
    public AwaitSignal(int loopNumber){
        this.loopNumber = loopNumber;
    }
    public void print(String msg, Condition current, Condition next){
        for(int i = 0; i < loopNumber; i++){
            this.lock();
            try {
                current.await();
                System.out.print(msg);
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                unlock();
            }
        }
    }
    public void start(Condition d){
        this.lock();
        try {
            log.debug("start");
            d.signal();
        }finally {
            this.unlock();
        }
    }
}
park unpark
public class Test27_Control_Thread_Order_Park {
    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{
    private int loopNumber;
    public ParkUnpark(int loopNumber){
        this.loopNumber = loopNumber;
    }
    public void print(String msg, Thread next){
        for(int i = 0; i < loopNumber; i++){
            LockSupport.park();
            System.out.println(msg);
            LockSupport.unpark(next);
        }
    }
}

4.12 本章小结

  • 多线程访问共享资源时,哪些代码片段是属于临界区
  • 使用sychronized互斥解决临界区线程安全问题
    • 掌握synchronized对象锁语法
    • 掌握synchronized加载成员方法和静态方法语法
    • 掌握wait/notify同步方法
  • 使用ReentrantLock互斥解决临界区线程安全问题
    • 掌握使用细节:可打断、锁超时、公平锁、条件变量
  • 学会分析变量的线程安全性,掌握常用的线程安全类
  • 了解县城活跃性问题:死锁、活锁、饥饿
  • 应用
    • 互斥:使用synchronized或ReentrantLock达到动向资源的互斥效果
    • 同步:使用wait/notify或ReentrantLock的条件变量来达到线程间的通信效果
  • 原理方面
    • monitor、synchronized、wait/notify原理
    • synchronized进阶原理(轻量级、重量级Monitor、锁消除、自旋优化)
    • park&unpark原理
  • 模式方面
    • 同步模式之保护暂停
    • 异步模式之生产者消费者
    • 同步模式之顺序控制
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值