Java并发编程学习笔记3——synchronized原理

目录

1、共享带来的问题

1.1、临界区(Critical Section)

1.2、竞态条件(Race Condition)

2、synchronize解决方案

2.1、应用之互斥

2.2、synchronized

2.3、面向对象改进

3、方法上的synchronized

4、变量的线程安全分析

4.1、成员变量和静态变量是否线程安全?

4.2、局部变量是否线程安全?

4.3、常见线程安全类

4.3.1、线程安全类方法的组合

4.3.2、不可变类线程安全性

5、Monitor(锁)概念

5.1、Java对象头

5.2、Monitor(锁)

6、synchronized原理进阶

6.1、轻量级锁

6.2、锁膨胀

6.3、自旋优化

6.4、偏向锁

6.4.1、偏向状态

6.4.2、偏向锁撤销 - 调用对象hashCode

6.4.3、偏向锁撤销 - 其它线程使用对象

6.4.4、偏向锁撤销 - 调用wait/notify

6.4.5、批量重定向

6.4.6、批量撤销

7、wait() / notify()

7.1、wait() / notify() 原理

7.2、API介绍

7.3、wait() / notify() 的正确使用姿势

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

7.3.2、使用wait() / notify() 的套路

7.3.3、同步模式——保护性暂停

7.3.4、扩展

7.3.5、异步模式之生产者/消费者

9、park() / unpark()

9.1、基本使用

9.2、特点

9.3、原理之park & unpark

9.3.1、情况1:NEW -> RUNNABLE

9.3.2、情况2:RUNNABLE -> WAITING

9.3.3、情况3:RUNNABLE <--> WAITING

9.3.4、情况4:RUNNABLE <--> WAITING

9.3.5、情况5:RUNNABLE <--> TIMED_WAITING

9.3.6、情况6:RUNNABLE <--> TIMED_WAITING

9.3.7、情况7:RUNNABLE <--> TIMED_WAITING

9.3.8、情况8:RUNNABLE <--> TIMED_WAITING

9.3.9、情况9:RUNNABLE <--> BLOCKED

9.3.10、情况10:RUNNABLE <--> TERMINATED

10、多把锁

10.1、多把不相干的锁

11、活跃性

11.1、死锁

11.2、定位死锁

11.3、哲学家就餐问题

11.4、活锁

11.5、饥饿

12、ReentranLock

12.1、可重入

12.2、可打断

12.3、锁超时

12.3.1、锁超时解决哲学家就餐问题

12.4、公平锁

12.5、条件变量

13、同步模式之顺序控制

13.1、固定运行顺序

13.2、交替输出

13.2.1、wait & notify 版

13.2.2、await & signal 版(ReentranLock)

12.2.3、park & unpark版


1、共享带来的问题

两个线程对初始值为0的静态变量一个做自增,一个做自减,各做5000次,结果是0吗?

@Slf4j
public class Test1 {
    static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count--;
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", count);
    }
}

结果:
14:37:13.111 [main] DEBUG com.bfbc.test3.Test1 - -2314

1.1、临界区(Critical Section)

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

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

static int counter = 0;

static void increment() 
// 临界区
{
    counter++;
}

static void decrement()
// 临界区
{
    counter--;
}

1.2、竞态条件(Race Condition)

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

2、synchronize解决方案

2.1、应用之互斥

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

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

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

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

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

2.2、synchronized

语法:
synchronized(对象) {
    临界区
}

解决:

@Slf4j
public class Test1 {
    static int count = 0;
    private static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    count++;
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    count--;
                }
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", count);
    }
}

结果;
15:01:37.907 [main] DEBUG com.bfbc.test3.Test1 - 0

synchronized实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

2.3、面向对象改进

把需要保护的共享变量放入一个类。

public class Room {
    int value = 0;

    public void increment() {
        synchronized (this) {
            value ++;
        }
    }

    public void decrement() {
        synchronized (this) {
            value --;
        }
    }

    public int get() {
        synchronized (this) {
            return value;
        }
    }
}

@Slf4j
public class Test1 {
    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("{}", room.get());
    }
}

结果:
15:12:14.613 [main] DEBUG com.bfbc.test3.Test1 - 0

3、方法上的synchronized

class Test {
    public synchronized void test() {
    }
}
等价于:
class Test {
    public void test() {
        synchronized(this) {
        }
    }
}


class Test {
    public synchronized static void test() {

    }
}
等价于:
class Test {
    public static void test() {
        synchronized(Test.class) {
        }
    }
}

不加synchronized的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去的)。

4、变量的线程安全分析

4.1、成员变量和静态变量是否线程安全?

  • 如果它们没有共享,则线程安全;
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况:如果只有读操作,则线程安全;如果有读写操作,则这段代码就是临界区,需要考虑线程安全。

4.2、局部变量是否线程安全?

  • 局部变量是线程安全的;
  • 但局部变量引用的对象则未必:如果该对象没有逃离方法的作用访问,它是线程安全的;如果该对象逃离方法的作用范围,需要考虑线程安全。

4.3、常见线程安全类

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

这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的,也可以理解为:它们的每个方法是原子的,但注意它们多个方法的组合不是原子的。

Hashtable table = new Hashtable();

new Thread(() -> {
    table.put("key", "value1);
});

new Thread(() -> {
    tbale.put("key", "value2");
});

4.3.1、线程安全类方法的组合

分析下面代码是否线程安全?
Hashtable table = new Hashtable();
// 线程1,线程2
if (table.get("key") == null) {
    table.put("key", value);
}

4.3.2、不可变类线程安全性

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

5、Monitor(锁)概念

5.1、Java对象头

以64位虚拟机为例。

5.2、Monitor(锁)

Monitor被翻译为监视器或管程。

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

  • 刚开始Monitor中Owner为null;
  • 当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner;
  • 在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED;
  • Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争是非公平的;
  • 图中WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,后面讲wait-notify时会分析。

注意:

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

6、synchronized原理进阶

6.1、轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么就可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是synchronized。

   // 假设有两个方法同步块,利用同一个对象加锁
    static final Object obj = new Object();
    public static void method1() {
        synchronized (obj) {
            // 同步块 A
            method2();
        }
    }
    public static void method2() {
        synchronized (obj) {
            // 同步块 B
        }
    }

1、创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word;

2、让锁记录中Object reference指向锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录;

3、如果cas替换成功,对象头中存储了锁记录地址和状态00,表示由该线程给对象加锁,这时图示如下:

4、如果cas替换失败,有两种情况:

  • 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程;
  • 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数。

5、当退出synchronized代码块(解锁)时,如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一。

6、当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头。成功,则解锁成功;失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。

6.2、锁膨胀

如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

static Object obj = new Object();
public static void method1() {
    synchronized(obj) {
        // 同步块
    }
}

1、当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁。

这时Thread-1加轻量级锁失败,进入锁膨胀流程:

  • 即为Object对象申请Monitor锁,让Object指向重量级锁地址;
  • 然后自己进入Monitor的EntryList BLOCKED。

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

6.3、自旋优化

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

自旋重试成功的情况:

线程1(CPU 1上)对象 Mark线程2(CPU 2上)
-10(重量锁)-
访问同步块,获取monitor10(重量锁) 重量锁指针-
成功(加锁)10(重量锁) 重量锁指针-
执行同步块10(重量锁) 重量锁指针-
执行同步块10(重量锁) 重量锁指针访问同步块,获取monitor
执行同步块10(重量锁) 重量锁指针自旋重试
执行完毕10(重量锁) 重量锁指针自旋重试
成功(解锁)01(无锁)自旋重试
-10(重量锁) 重量锁指针成功(加锁)
-10(重量锁) 重量锁指针执行同步块
-......

自旋重试失败的情况:

线程1(CPU 1上)对象Mark线程2(CPU 2上)
-10(重量锁)-
访问同步块,获取monitor10(重量锁) 重量锁指针-
成功(加锁)10(重量锁) 重量锁指针-
执行同步块10(重量锁) 重量锁指针-
执行同步块10(重量锁) 重量锁指针访问同步块,获取monitor
执行同步块10(重量锁) 重量锁指针自旋重试
执行同步块10(重量锁) 重量锁指针自旋重试
执行同步块10(重量锁) 重量锁指针自旋重试
执行同步块10(重量锁) 重量锁指针阻塞
-......
  • 在Java 6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能;
  • 自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势;
  • Java 7之后不能控制是否开启自旋功能。

6.4、偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然要执行CAS操作。

Java 6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归该线程所有。

6.4.1、偏向状态

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword值为0x05即最后3位为101,这时它的thread、epoch、age都为0;
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想要避免延迟,可以加VM参数 - xx:BiasedLockingStartupDelay=0来禁用延迟;
  • 如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值;

6.4.2、偏向锁撤销 - 调用对象hashCode

调用了对象的hashCode,但偏向锁的对象MarkWord中存储的是线程id,如果调用hashCode会导致偏向锁被撤销。

  • 轻量级锁会在锁记录中记录hashCode;
  • 重量级锁会在Monitor中记录hashCode;

在调用hashCode后使用偏向锁,记得去掉 -xx:-UseBiasedLocking

6.4.3、偏向锁撤销 - 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。

@Slf4j
public class TestBiased2 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread t1 = new Thread(()->{
            log.debug(ClassLayout.parseInstance(dog).toPrintable());
            synchronized (dog) {
                log.debug(ClassLayout.parseInstance(dog).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(dog).toPrintable());

            synchronized (TestBiased2.class) {
                TestBiased2.class.notify();
            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(()->{
            synchronized (TestBiased2.class) {
                try {
                    TestBiased2.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            log.debug(ClassLayout.parseInstance(dog).toPrintable());
            synchronized (dog) {
                log.debug(ClassLayout.parseInstance(dog).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(dog).toPrintable());
        },"t2");
        t2.start();
    }
}

6.4.4、偏向锁撤销 - 调用wait/notify

6.4.5、批量重定向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID。当撤销偏向锁阈值超过20次后,JVM会在给这些对象加锁时重新偏向至加锁线程。

@Slf4j
public class TestBiased3 {
    public static void main(String[] args) {
        Vector<Dog> list = new Vector<>();
        Thread t1 = new Thread(()->{
            for (int i=0; i<30; i++) {
                Dog dog = new Dog();
                list.add(dog);
                synchronized (dog) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
                }
            }
            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 dog = list.get(i);
                log.debug(i + "\t" +ClassLayout.parseInstance(dog).toPrintable());
                synchronized (dog) {
                    log.debug((i + "\t" + ClassLayout.parseInstance(dog).toPrintable()));
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(dog).toPrintable());
            }
        }, "t2");
        t2.start();
    }
}

6.4.6、批量撤销

当撤销偏向锁阈值超过40次后,JVM会这样觉得,自己确实是偏向错了,根本就不该偏向。于是整个类的所有对象都变为不可偏向的,新建的对象也是不可偏向的。

7、wait() / notify()

7.1、wait() / notify() 原理

  • Owner线程发现条件不满足,调用wait()方法,即可进入WaitSet变为WAITING状态;
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片;
  • BLOCKED线程会在Owner线程释放锁时唤醒;
  • WAITING线程会在Owner线程调用notify()或notifyAll()时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争。

7.2、API介绍

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

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

@Slf4j
public class TestBiased4 {
    final static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                log.debug("执行...");
                try {
                    lock.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码...");
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(()->{
            synchronized (lock) {
                log.debug("执行...");
                try {
                    lock.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码...");
            }
        }, "t2");
        t2.start();

        // 主线程两秒后执行
        Thread.sleep(2000);
        log.debug("唤醒 obj 上其他线程");
        synchronized (lock) {
            lock.notify(); // 唤醒lock上的一个线程
//            lock.notifyAll(); // 唤醒lock上的所有线程
        }
    }
}

结果:
16:51:38.839 [t1] DEBUG com.bfbc.test4.TestBiased4 - 执行...
16:51:38.841 [t2] DEBUG com.bfbc.test4.TestBiased4 - 执行...
16:51:40.837 [main] DEBUG com.bfbc.test4.TestBiased4 - 唤醒 obj 上其他线程
16:51:40.837 [t1] DEBUG com.bfbc.test4.TestBiased4 - 其他代码...

7.3、wait() / notify() 的正确使用姿势

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

  • sleep是Thread方法,而wait是Object的方法;
  • sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起使用;
  • sleep在睡眠的同时,不会释放对象锁,但wait在等待的时候会释放对象锁。
@Slf4j
public class TestBiased5 {
    static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                log.debug("获得锁");
                try {
                    Thread.sleep(10000);
//                    lock.wait(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(1000);
        synchronized (lock) {
            log.debug("获得锁");
        }
    }
}

结果:
17:08:46.939 [t1] DEBUG com.bfbc.test4.TestBiased5 - 获得锁
17:08:47.945 [main] DEBUG com.bfbc.test4.TestBiased5 - 获得锁
@Slf4j
public class TestBiased6 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了...");
                }
            }
        }, "小南");
        t1.start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }

        Thread.sleep(1000);
        new Thread(()->{
            hasCigarette = true;
            log.debug("烟到了");
        }, "送烟的").start();
    }
}

结果:
18:41:54.749 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[false]
18:41:54.752 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 没烟,先歇会!
18:41:55.748 [送烟的] DEBUG com.multiThreads.test4.TestBiased6 - 烟到了
18:41:56.753 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[true]
18:41:56.753 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了...
18:41:56.753 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:41:56.753 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:41:56.753 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:41:56.753 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:41:56.753 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了

其他干活的线程,都要一直阻塞,效率太低;

  • 小南线程必须睡足2s后才能醒来,就算烟提前送到,也无法立刻醒来;
  • 加了synchronized(room)之后,就好比小南在房间里面反锁了门睡觉,烟根本无法送进门,main没加synchronized就好像main线程是翻窗户进来的;
  • 解决方法:使用wait-notify机制。

解决方案:

@Slf4j
public class TestBiased6 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = 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("可以开始干活了...");
                }
            }
        }, "小南");
        t1.start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }

        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了");
                room.notify();
            }
        }, "送烟的").start();
    }
}

结果;
18:51:26.549 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[false]
18:51:26.552 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 没烟,先歇会!
18:51:26.553 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:51:26.553 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:51:26.553 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:51:26.553 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:51:26.553 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
18:51:27.557 [送烟的] DEBUG com.multiThreads.test4.TestBiased6 - 烟到了
18:51:27.557 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[true]
18:51:27.558 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了...

解决了其他干活的线程阻塞的问题。但如果有其他线程也在等待条件呢?

@Slf4j
public class TestBiased6 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = 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("没干成活...");
                }
            }
        }, "小南");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (room) {
                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("没干成活...");
                }
            }
        }, "小女");
        t2.start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }

        // 虚假唤醒
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了");
                room.notify();
            }
        }, "送外卖的").start();
    }
}

结果:
19:01:25.025 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[false]
19:01:25.030 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 没烟,先歇会!
19:01:25.030 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:01:25.030 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:01:25.030 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:01:25.030 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:01:25.030 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:01:25.030 [小女] DEBUG com.multiThreads.test4.TestBiased6 - 有外卖没?[false]
19:01:25.030 [小女] DEBUG com.multiThreads.test4.TestBiased6 - 没外卖,先歇会!
19:01:26.024 [送外卖的] DEBUG com.multiThreads.test4.TestBiased6 - 外卖到了
19:01:26.024 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[false]
19:01:26.024 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 没干成活...

解决虚假唤醒:

@Slf4j
public class TestBiased6 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = 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("没干成活...");
                }
            }
        }, "小南");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (room) {
                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("没干成活...");
                }
            }
        }, "小女");
        t2.start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }

        // 虚假唤醒
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了");
                room.notifyAll();
            }
        }, "送外卖的").start();
    }
}

结果:
19:05:30.114 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 有烟没?[false]
19:05:30.118 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 没烟,先歇会!
19:05:30.118 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:05:30.118 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:05:30.118 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:05:30.118 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:05:30.118 [其他人] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了
19:05:30.119 [小女] DEBUG com.multiThreads.test4.TestBiased6 - 有外卖没?[false]
19:05:30.119 [小女] DEBUG com.multiThreads.test4.TestBiased6 - 没外卖,先歇会!
19:05:31.114 [送外卖的] DEBUG com.multiThreads.test4.TestBiased6 - 外卖到了
19:05:31.115 [小女] DEBUG com.multiThreads.test4.TestBiased6 - 有外卖没?[true]
19:05:31.115 [小女] DEBUG com.multiThreads.test4.TestBiased6 - 可以开始干活了...
19:05:31.115 [小南] DEBUG com.multiThreads.test4.TestBiased6 - 没烟,先歇会!

7.3.2、使用wait() / notify() 的套路

synchronized(lock) {
    while(条件不成立) {
        lock.wait();
    }
    // 条件成立,继续执行
{

// 另一个线程使用 notifyAll()唤醒
synchronized(lock) {
    lock.notifyAll();
}

7.3.3、同步模式——保护性暂停

保护性暂停(Guarded Suspension),用在一个线程等待另一个线程的执行结果。

要点:

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

代码演示:

package com.multiThreads.test4;

import lombok.extern.slf4j.Slf4j;

class GuardedObject {
    private Object response;

    // 获取结果,timeOut:最大等待时间,超过这个时间即使没有拿到response的值也退出。
    public Object get(long timeOut) {
        synchronized (this) {
            // 开始时间
            long beginTime = System.currentTimeMillis();
            // 经历的时间
            long passedTime = 0;
            // 没有结果
            while(response == null) {
                // 经历的时间超过timeOut时,退出循环
                // waitTime:这一轮循环应等待的时间
                long waitTime = timeOut - passedTime;
                if (waitTime <= 0) break;
                try {
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 求得经历时间
                passedTime = System.currentTimeMillis() - beginTime;
            }
        }
        return response;
    }

    // 产生结果
    public void set(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            this.notifyAll();
        }
    }
}

@Slf4j
public class Test2 {
    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        // 线程1等待线程2的下载结果
        new Thread(()->{
            log.debug("等待结果");
            String result = (String)guardedObject.get(3000);
            log.debug("结果是:" + result);
        }, "t1").start();

        new Thread(()->{
            log.debug("执行下载...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.set(null);
        }, "t2").start();
    }
}

结果:
19:50:57.549 [t1] DEBUG com.multiThreads.test4.Test2 - 等待结果
19:50:57.549 [t2] DEBUG com.multiThreads.test4.Test2 - 执行下载...
19:51:00.566 [t1] DEBUG com.multiThreads.test4.Test2 - 结果是:null

7.3.4、扩展

图中Futures就好比居民楼一层的信箱(每个信箱都有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员。

如果需要在多个类之间使用GuardedObject对象,作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待着】和【结果生产者】,还能够同时支持多个任务的管理。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new People().start(); // 居民
        }
        Thread.sleep(1000);
        for (Integer id : MailBoxes.getIds()) {
            new Postman(id, "内容" + id).start(); // 邮递员
        }
    }
}

// 居民类
class People extends Thread{
    private static final Logger LOGGER = LoggerFactory.getLogger(People.class);

    @Override
    public void run() {
        // 收信
        GuardedObject guardedObject = MailBoxes.createGuardedObject();
        LOGGER.debug("开始收信 id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        LOGGER.debug("收到信 id:{},内容:{}", guardedObject.getId(), mail);
    }
}

// 邮递员类
class Postman extends Thread{
    private static final Logger LOGGER = LoggerFactory.getLogger(Postman.class);
    private int id;
    private String mail;

    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        GuardedObject guardedObject = MailBoxes.getGuardedObject(id);
        LOGGER.debug("送信 id:{},内容:{}", guardedObject.getId(), mail);
        guardedObject.set(mail);
    }
}

class MailBoxes {
    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();

    private static int id = 1;
    // 产生唯一id
    private 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 Object
    private int id;
    public int getId() {return id;}
    // 结果
    private Object response;

    public GuardedObject(int id) {
        this.id = id;
    }

    // 获取结果,timeOut:最大等待时间,超过这个时间即使没有拿到response的值也退出。
    public Object get(long timeOut) {
        synchronized (this) {
            // 开始时间
            long beginTime = System.currentTimeMillis();
            // 经历的时间
            long passedTime = 0;
            // 没有结果
            while(response == null) {
                // 经历的时间超过timeOut时,退出循环
                // waitTime:这一轮循环应等待的时间
                long waitTime = timeOut - passedTime;
                if (waitTime <= 0) break;
                try {
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 求得经历时间
                passedTime = System.currentTimeMillis() - beginTime;
            }
        }
        return response;
    }

    // 产生结果
    public void set(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            this.notifyAll();
        }
    }
}

结果;
19:23:24.248 [Thread-2] DEBUG com.multiThreads.test5.People - 开始收信 id:2
19:23:24.248 [Thread-0] DEBUG com.multiThreads.test5.People - 开始收信 id:3
19:23:24.248 [Thread-1] DEBUG com.multiThreads.test5.People - 开始收信 id:1
19:23:25.253 [Thread-3] DEBUG com.multiThreads.test5.Postman - 送信 id:3,内容:内容3
19:23:25.253 [Thread-4] DEBUG com.multiThreads.test5.Postman - 送信 id:2,内容:内容2
19:23:25.253 [Thread-5] DEBUG com.multiThreads.test5.Postman - 送信 id:1,内容:内容1
19:23:25.253 [Thread-2] DEBUG com.multiThreads.test5.People - 收到信 id:2,内容:内容2
19:23:25.253 [Thread-1] DEBUG com.multiThreads.test5.People - 收到信 id:1,内容:内容1
19:23:25.253 [Thread-0] DEBUG com.multiThreads.test5.People - 收到信 id:3,内容:内容3

7.3.5、异步模式之生产者/消费者

要点:

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

代码演示:

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        MessageQueue messageQueue = new MessageQueue(2);

        // 生产者往消息队列放入消息
        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(()->{
                messageQueue.put(new Message(id, "值" + id));
            }, "生产者线程" + i).start();
        }

        new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                    Message message = messageQueue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者线程").start();
    }
}

// 消息队列类,Java线程之间通信
class MessageQueue {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageQueue.class);
    // 消息的队列集合
    private LinkedList<Message> list = new LinkedList<>();
    // 队列的容量
    private int capacity;

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    // 获取消息
    public Message take() {
        // 检查队列是否为空
        synchronized (list) {
            while (list.isEmpty()) {
                try {
                    LOGGER.debug("队列为空,消费者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 从队列的头部获取消息并返回
            Message message = list.removeFirst();
            LOGGER.debug("已消费消息: {}", message);
            list.notifyAll();
            return message;
        }
    }

    // 存入消息
    public void put(Message message) {
        // 检查队列是否为满
        synchronized (list) {
            while (list.size() == capacity) {
                try {
                    LOGGER.debug("队列已满,生产者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 将新的消息加入队列的尾部
            list.addLast(message);
            LOGGER.debug("已生产消息: {}", message);
            list.notifyAll();
        }
    }
}

// 设计Message
@Data
final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}

结果:
20:03:08.145 [生产者线程0] DEBUG com.multiThreads.test5.MessageQueue - 已生产消息: Message{id=0, value=值0}
20:03:08.145 [生产者线程1] DEBUG com.multiThreads.test5.MessageQueue - 已生产消息: Message{id=1, value=值1}
20:03:08.145 [生产者线程2] DEBUG com.multiThreads.test5.MessageQueue - 队列已满,生产者线程等待
20:03:09.147 [消费者线程] DEBUG com.multiThreads.test5.MessageQueue - 已消费消息: Message{id=0, value=值0}
20:03:09.147 [生产者线程2] DEBUG com.multiThreads.test5.MessageQueue - 已生产消息: Message{id=2, value=值2}
20:03:10.148 [消费者线程] DEBUG com.multiThreads.test5.MessageQueue - 已消费消息: Message{id=1, value=值1}
20:03:11.149 [消费者线程] DEBUG com.multiThreads.test5.MessageQueue - 已消费消息: Message{id=2, value=值2}
20:03:12.150 [消费者线程] DEBUG com.multiThreads.test5.MessageQueue - 队列为空,消费者线程等待

9、park() / unpark()

9.1、基本使用

它们是LockSupport类中的方法。

// 暂停当前线程
LockSupport.park();

// 恢复某个线程的运行
LockSupport.unpack();
public class TestParkUnpark {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestParkUnpark.class);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                LOGGER.debug("start");
                Thread.sleep(1000);
                LOGGER.debug("park");
                LockSupport.park(); // 对应线程状态为wait
                LOGGER.debug("resume");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();

        Thread.sleep(2000);
        LOGGER.debug("unpark..");
        LockSupport.unpark(t1);
    }
}

结果:
14:37:09.913 [Thread-0] DEBUG com.multiThreads.test6.TestParkUnpark - start
14:37:10.915 [Thread-0] DEBUG com.multiThreads.test6.TestParkUnpark - park
14:37:11.912 [main] DEBUG com.multiThreads.test6.TestParkUnpark - unpark..
14:37:11.912 [Thread-0] DEBUG com.multiThreads.test6.TestParkUnpark - resume

9.2、特点

与Object的wait & notify 相比:

  • wait,notify和notifyAll必须配合Object Monitor一起使用,而unpark不必;
  • park & unpark是以线程为单位来【阻塞】和【唤醒】线程,而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】;
  • park & unpark可以先unpark,而wait & notify不能先notify。

9.3、原理之park & unpark

每个线程都有自己的一个Parker对象,由三部分组成_counter, _cond 和 _mutex。打个比喻:

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

假设有线程Thread t。

9.3.1、情况1:NEW -> RUNNABLE

当调用t.start()方法时,由NEW -> RUNNABLE

9.3.2、情况2:RUNNABLE -> WAITING

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

  • 调用obj.wait()方法时,t线程从RUNNABLE -> WAITING;
  • 调用obj.notify(),obj.notifyAll(),t.interrupt()时:① 竞争锁成功,t线程从WAITING -> RUNNABLE;② 竞争锁失败,t线程从WAITING -> BLOCKED。
public class TestWaitNotify {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestWaitNotify.class);
    private final static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()-> {
            synchronized (obj) {
                LOGGER.debug("执行...");
                try {
                    obj.wait();
                    LOGGER.debug("其他代码...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "t1").start();

        new Thread(()-> {
            synchronized (obj) {
                LOGGER.debug("执行...");
                try {
                    obj.wait();
                    LOGGER.debug("其他代码...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }, "t2").start();

        // 主线程两秒后执行
        Thread.sleep(500);
        LOGGER.debug("唤醒obj上其他线程");
        synchronized (obj) {
            obj.notifyAll();
        }
    }
}

结果:
15:43:40.822 [t1] DEBUG com.multiThreads.test6.TestWaitNotify - 执行...
15:43:40.825 [t2] DEBUG com.multiThreads.test6.TestWaitNotify - 执行...
15:43:41.320 [main] DEBUG com.multiThreads.test6.TestWaitNotify - 唤醒obj上其他线程
15:43:41.320 [t2] DEBUG com.multiThreads.test6.TestWaitNotify - 其他代码...
15:43:42.321 [t1] DEBUG com.multiThreads.test6.TestWaitNotify - 其他代码...

9.3.3、情况3:RUNNABLE <--> WAITING

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

9.3.4、情况4:RUNNABLE <--> WAITING

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

9.3.5、情况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。

9.3.6、情况6:RUNNABLE <--> TIMED_WAITING

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

9.3.7、情况7:RUNNABLE <--> TIMED_WAITING

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

9.3.8、情况8:RUNNABLE <--> TIMED_WAITING

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

9.3.9、情况9:RUNNABLE <--> BLOCKED

  • t线程用synchronized(obj)获取了对象锁时如果竞争失败,从RUNNABLE --> BLOCKED;
  • 持obj锁线程的同步代码块执行完毕,会唤醒该对象上所有BLOCKED的线程重新竞争,如果其中t线程。

9.3.10、情况10:RUNNABLE <--> TERMINATED

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

10、多把锁

10.1、多把不相干的锁

一间大屋子有两个功能:睡觉、学习。互不相干。

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

例如:

public class TestMultiLock {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        }, "小南").start();
        new Thread(() -> {
            bigRoom.sleep();
        }, "小女").start();
    }
}

class BigRoom {
    private static final Logger LOGGER = LoggerFactory.getLogger(BigRoom.class);

    private final Object studyRoom = new Object();
    private final Object bedRoom = new Object();

    // 睡觉
    public void sleep() {
        synchronized (bedRoom) {
            LOGGER.debug("sleeping 2 秒");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 学习
    public void study() {
        synchronized (studyRoom) {
            LOGGER.debug("study 1 秒");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:
16:29:52.438 [小女] DEBUG com.multiThreads.test7.BigRoom - sleeping 2 秒
16:29:52.438 [小南] DEBUG com.multiThreads.test7.BigRoom - study 1 秒

将锁的粒度细分:

  • 好处:可以增加并发度;
  • 坏处:如果一个线程需要同时获得多把锁,就容易发生死锁。

11、活跃性

11.1、死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁。

  • t1线程获得A对象锁,接下来想获取B对象的锁;
  • t2线程获得B对象锁,接下来想获取A对象的锁。

例:

public class DeadLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(DeadLock.class);
    private static Object A = new Object();
    private static Object B = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (A) {
                LOGGER.debug("Lock A");
                sleep(1);
                synchronized (B) {
                    LOGGER.debug("Lock B");
                    LOGGER.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(()->{
            synchronized (B) {
                LOGGER.debug("Lock B");
                sleep(0.5);
                synchronized (A) {
                    LOGGER.debug("Lock B");
                    LOGGER.debug("操作...");
                }
            }
        }, "t2");

        t1.start();
        t2.start();
    }

    private static void sleep(double time) {
        try {
            Thread.sleep((long)(time * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:
18:30:00.623 [t1] DEBUG com.multiThreads.test8.DeadLock - Lock A
18:30:00.623 [t2] DEBUG com.multiThreads.test8.DeadLock - Lock B

11.2、定位死锁

检测死锁可以使用jconsole工具,或者使用jps定位进程id,再用jstack定位死锁。

cmd > jps
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
12320 Jps
22816 KotlinCompileDaemon
33200 TestDeadLock // JVM进程
11508 Main
28468 Launcher

11.3、哲学家就餐问题

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

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

筷子类:

public class TestPhilosopher {
    public static void main(String[] args) {
        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();
    }
}

/**
 * 哲学家就餐问题
 */
class Philosopher extends Thread {
    private static final Logger LOGGER = LoggerFactory.getLogger(Philosopher.class);
    private Chopstick left;
    private Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            synchronized (left) {
                // 尝试获得右手筷子
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    private void eat() {
        LOGGER.debug("eating...");
        sleep0(1);
    }

    private void sleep0(double time) {
        try {
            Thread.sleep((long)(time * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + "}";
    }
}

结果:
18:53:55.500 [苏格拉底] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:55.500 [亚里士多德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:56.504 [亚里士多德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:56.504 [阿基米德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:57.504 [阿基米德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:57.504 [柏拉图] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:58.504 [柏拉图] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:58.504 [赫拉克利特] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:59.505 [亚里士多德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:53:59.505 [苏格拉底] DEBUG com.multiThreads.test8.Philosopher - eating...
18:54:00.506 [阿基米德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:54:00.506 [柏拉图] DEBUG com.multiThreads.test8.Philosopher - eating...
18:54:01.506 [柏拉图] DEBUG com.multiThreads.test8.Philosopher - eating...
18:54:01.506 [赫拉克利特] DEBUG com.multiThreads.test8.Philosopher - eating...
18:54:02.506 [柏拉图] DEBUG com.multiThreads.test8.Philosopher - eating...
18:54:02.506 [赫拉克利特] DEBUG com.multiThreads.test8.Philosopher - eating...
// 此处发生了死锁,程序停在这里

11.4、活锁

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

public class TestLiveLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestLiveLock.class);
    static volatile int count = 0;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            // 期望减到-10退出循环
            while(count > -10) {
                sleep(0.2);
                count--;
                LOGGER.debug("count: {}", count);
            }
        },"t1").start();

        new Thread(()->{
            // 期望超过10退出循环
            while(count < 10) {
                sleep(0.2);
                count++;
                LOGGER.debug("count: {}", count);
            }
        },"t2").start();
    }

    private static void sleep(double time) {
        try {
            Thread.sleep((long) (time * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:
19:06:09.100 [t2] DEBUG com.multiThreads.test9.TestLiveLock - count: 0
19:06:09.100 [t1] DEBUG com.multiThreads.test9.TestLiveLock - count: -1
19:06:09.306 [t2] DEBUG com.multiThreads.test9.TestLiveLock - count: 1
19:06:09.306 [t1] DEBUG com.multiThreads.test9.TestLiveLock - count: 0
19:06:09.506 [t1] DEBUG com.multiThreads.test9.TestLiveLock - count: -1
... // 一直在0左右徘徊

11.5、饥饿

很多教程中把饥饿定义为:一个线程由于优先级太低,始终得不到CPU调度执行,也不能够结束,饥饿的情况不易演示。

12、ReentranLock

相对于synchronized,ReentranLock具备如下特点:

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

与synchronized一样,都支持可重入。

12.1、可重入

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

public class TestReentrantLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestReentrantLock.class);
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        // 获取锁
        lock.lock();
        try {
            // 临界区
            LOGGER.debug("execute method1");
            method2();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    private static void method2() {
        // 获取锁
        lock.lock();
        try {
            // 临界区
            LOGGER.debug("execute method2");
            method3();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    private static void method3() {
        // 获取锁
        lock.lock();
        try {
            // 临界区
            LOGGER.debug("execute method3");
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

结果:
19:29:27.249 [main] DEBUG com.multiThreads.test10.TestReentrantLock - execute method1
19:29:27.251 [main] DEBUG com.multiThreads.test10.TestReentrantLock - execute method2
19:29:27.251 [main] DEBUG com.multiThreads.test10.TestReentrantLock - execute method3

12.2、可打断

public class TestReentrantLock1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestReentrantLock1.class);
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                // 如果没有竞争那么此方法就会获取lock对象锁
                // 如果有竞争就进去阻塞队列,可以被其他线程用interrupt()方法打断
                LOGGER.debug("尝试获得锁...");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                LOGGER.debug("没有获得锁");
                return;
            }
            try {
                LOGGER.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();

        Thread.sleep(1000);
        LOGGER.debug("打断 t1...");
        t1.interrupt();
    }
}

结果:
19:39:49.609 [t1] DEBUG com.multiThreads.test10.TestReentrantLock1 - 尝试获得锁...
19:39:50.587 [main] DEBUG com.multiThreads.test10.TestReentrantLock1 - 打断 t1...
19:39:50.588 [t1] DEBUG com.multiThreads.test10.TestReentrantLock1 - 没有获得锁

12.3、锁超时

立刻失败。

public class TestReentrantLock3 {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestReentrantLock3.class);
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            LOGGER.debug("启动...");
            // 尝试获得锁,如果成功就获得锁;如果失败就不进入阻塞队列等待
            // 另一种方式:lock.tryLock(2, TimeUnit.SECONDS),表示尝试等多久,如2秒内没后获得锁就返回false
            if (!lock.tryLock()) { // 这里是立刻返回获取结果
                LOGGER.debug("获取立刻失败,返回");
                return;
            }
            try {
                LOGGER.debug("获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        t1.start();
    }
}

结果:
19:54:15.046 [t1] DEBUG com.multiThreads.test10.TestReentrantLock3 - 启动...
19:54:15.061 [t1] DEBUG com.multiThreads.test10.TestReentrantLock3 - 获取立刻失败,返回

12.3.1、锁超时解决哲学家就餐问题

public class TestPhilosopher {
    public static void main(String[] args) {
        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();
    }
}

/**
 * 哲学家就餐问题
 */
class Philosopher extends Thread {
    private static final Logger LOGGER = LoggerFactory.getLogger(Philosopher.class);
    private Chopstick left;
    private Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获取左手筷子
            if (left.tryLock()) {
                try {
                    //尝试获取右手筷子
                    if(right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        LOGGER.debug("eating...");
        sleep0(1);
    }

    private void sleep0(double time) {
        try {
            Thread.sleep((long)(time * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + "}";
    }
}

结果:
18:45:16.394 [苏格拉底] DEBUG com.multiThreads.test8.Philosopher - eating...
18:45:16.394 [亚里士多德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:45:17.405 [阿基米德] DEBUG com.multiThreads.test8.Philosopher - eating...
18:45:17.409 [柏拉图] DEBUG com.multiThreads.test8.Philosopher - eating...
...

12.4、公平锁

ReentranLock默认是不公平的。

12.5、条件变量

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

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

使用流程:

  • await前需要获得锁;
  • await执行后,会释放锁,进入conditionObject;
  • await的线程被唤醒(或打断、或超时)去重新竞争lock锁;
  • 竞争lock锁成功后,从await后继续执行。
public class Test1 {
    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // 创建2个新的条件变量(休息室)
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        
        // 先获得锁
        lock.lock();
        // 进入休息室等待,await()执行后,会释放锁
        condition1.await();

        condition1.signal(); // 唤醒条件变量condition1中的某一个线程
        condition1.signalAll(); // 唤醒条件变量condition2中的某一个线程
    }
}
public class TestCondition {
    static final Logger LOGGER = LoggerFactory.getLogger(TestCondition.class);
    static final Object room = new Object();
    static boolean hasCigarette = false; // 烟
    static boolean hasTakeout = false; // 是否有外卖
    static ReentrantLock ROOM = new ReentrantLock(); // 锁
    static Condition waitCigaretteSet = ROOM.newCondition(); // 等待烟的休息室
    static Condition waitTakeoutSet = ROOM.newCondition(); // 等待外卖的休息室

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            ROOM.lock();
            try {
                LOGGER.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
                    LOGGER.debug("没烟,先歇会!");
                    waitCigaretteSet.await();
                }
                LOGGER.debug("可以开始干活了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                ROOM.unlock();
            }
        },"小南").start();


        new Thread(()->{
            ROOM.lock();
            try {
                LOGGER.debug("有外卖没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    LOGGER.debug("没烟,先歇会!");
                    waitTakeoutSet.await();
                }
                LOGGER.debug("可以开始干活了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                ROOM.unlock();
            }
        },"小女").start();

        // 模拟送烟和送外卖的线程
        Thread.sleep(1000);
        new Thread(()->{
            ROOM.lock();
            try {
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
                ROOM.unlock();
            }
        },"送烟的").start();
        Thread.sleep(1000);
        new Thread(()->{
            ROOM.lock();
            try {
                hasTakeout = true;
                waitTakeoutSet.signal();
            } finally {
                ROOM.unlock();
            }
        },"送外卖的").start();
    }
}

结果;
19:26:24.433 [小南] DEBUG com.multiThreads.test11.TestCondition - 有烟没?[false]
19:26:24.438 [小南] DEBUG com.multiThreads.test11.TestCondition - 没烟,先歇会!
19:26:24.438 [小女] DEBUG com.multiThreads.test11.TestCondition - 有外卖没?[false]
19:26:24.438 [小女] DEBUG com.multiThreads.test11.TestCondition - 没烟,先歇会!
19:26:25.434 [小南] DEBUG com.multiThreads.test11.TestCondition - 可以开始干活了
19:26:26.436 [小女] DEBUG com.multiThreads.test11.TestCondition - 可以开始干活了

13、同步模式之顺序控制

13.1、固定运行顺序

比如,必须先2后1打印。

public class Test1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Test1.class);
    static final Object lock = new Object();
    static boolean t2Runned = false; // 表示t2是否运行过

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (lock) {
                while (!t2Runned) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                LOGGER.debug("1");
            }
        },"t1");

        Thread t2 = new Thread(()->{
            synchronized (lock) {
                LOGGER.debug("2");
                t2Runned = true;
                lock.notify();
            }
        },"t2");

        t1.start();
        t2.start();
    }
}

结果:
19:47:19.615 [t2] DEBUG com.multiThreads.test12.Test1 - 2
19:47:19.615 [t1] DEBUG com.multiThreads.test12.Test1 - 1
public class Test2 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Test2.class);
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            LOGGER.debug("1");
        }, "t1");
        t1.start();

        Thread t2 = new Thread(()->{
            LOGGER.debug("2");
            LockSupport.unpark(t1);
        },"t2");
        t2.start();
    }
}

结果:
19:51:17.644 [t2] DEBUG com.multiThreads.test12.Test2 - 2
19:51:17.647 [t1] DEBUG com.multiThreads.test12.Test2 - 1

13.2、交替输出

线程1输出“a”5次,线程2输出“b”5次,线程3输出“c”5次。现在要求输出abcabcabc怎么实现。

13.2.1、wait & notify 版

public class Test {
    public static void main(String[] args) {
        WaitNotify waitNotify = new WaitNotify(1, 3);

        new Thread(()->{
            waitNotify.print(1, 2, "a");
        }).start();
        new Thread(()->{
            waitNotify.print(2, 3, "b");
        }).start();
        new Thread(()->{
            waitNotify.print(3, 1, "c");
        }).start();
    }
}

/**
 * 输出内容     等待标记    下一个标记
 *   a           1          2
 *   b           2          3
 *   c           3          1
 */
class WaitNotify {
    private static final Logger LOGGER = LoggerFactory.getLogger(WaitNotify.class);
    // 等待标记
    private int flag;
    // 循环次数
    private int loopNumber;

    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    // 打印
    public void print(int waitFlag, int nextFlag, String str) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while(this.flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                LOGGER.debug(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
}

结果:
18:45:28.340 [Thread-0] DEBUG com.multiThreads.test12.WaitNotify - a
18:45:28.340 [Thread-1] DEBUG com.multiThreads.test12.WaitNotify - b
18:45:28.340 [Thread-2] DEBUG com.multiThreads.test12.WaitNotify - c
18:45:28.340 [Thread-0] DEBUG com.multiThreads.test12.WaitNotify - a
18:45:28.340 [Thread-1] DEBUG com.multiThreads.test12.WaitNotify - b
18:45:28.340 [Thread-2] DEBUG com.multiThreads.test12.WaitNotify - c
18:45:28.340 [Thread-0] DEBUG com.multiThreads.test12.WaitNotify - a
18:45:28.340 [Thread-1] DEBUG com.multiThreads.test12.WaitNotify - b
18:45:28.340 [Thread-2] DEBUG com.multiThreads.test12.WaitNotify - c

13.2.2、await & signal 版(ReentranLock)

public class Test3 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Test3.class);

    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(3);
        Condition a = awaitSignal.newCondition();
        Condition b = awaitSignal.newCondition();
        Condition c = awaitSignal.newCondition();

        new Thread(()->{
            awaitSignal.print("a", a, b);
        }).start();
        new Thread(()->{
            awaitSignal.print("b", b, c);
        }).start();
        new Thread(()->{
            awaitSignal.print("c", c, a);
        }).start();

        // 主线程先唤醒一个线程
        Thread.sleep(1000);
        awaitSignal.lock();
        try {
            LOGGER.debug("开始...");
            a.signal();
        } finally {
            awaitSignal.unlock();
        }
    }
}

class AwaitSignal extends ReentrantLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(AwaitSignal.class);

    // 循环次数
    private int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    /**
     * 打印内容
     * @param str:要打印的内容
     * @param current:进入哪一间休息室
     * @param next:下一间休息室
     */
    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                current.await();
                LOGGER.debug(str);
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                unlock();
            }
        }
    }
}

结果:
18:59:14.282 [main] DEBUG com.multiThreads.test12.Test3 - 开始...
18:59:14.282 [Thread-0] DEBUG com.multiThreads.test12.AwaitSignal - a
18:59:14.282 [Thread-1] DEBUG com.multiThreads.test12.AwaitSignal - b
18:59:14.282 [Thread-2] DEBUG com.multiThreads.test12.AwaitSignal - c
18:59:14.282 [Thread-0] DEBUG com.multiThreads.test12.AwaitSignal - a
18:59:14.282 [Thread-1] DEBUG com.multiThreads.test12.AwaitSignal - b
18:59:14.282 [Thread-2] DEBUG com.multiThreads.test12.AwaitSignal - c
18:59:14.282 [Thread-0] DEBUG com.multiThreads.test12.AwaitSignal - a
18:59:14.282 [Thread-1] DEBUG com.multiThreads.test12.AwaitSignal - b
18:59:14.282 [Thread-2] DEBUG com.multiThreads.test12.AwaitSignal - c

12.2.3、park & unpark版

public class Test4 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    
    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(3);
        t1 =  new Thread(()->{
            parkUnpark.print("a", t2);
        });
        t2 = new Thread(()->{
            parkUnpark.print("b", t3);
        });
        t3 = new Thread(()->{
            parkUnpark.print("c", t1);
        });
        
        t1.start();
        t2.start();
        t3.start();
        
        // 主线程发起
        LockSupport.unpark(t1);
    }
}

class ParkUnpark {
    private static final Logger LOGGER = LoggerFactory.getLogger(ParkUnpark.class);
    
    // 循环次数
    private int loopNumber;
    
    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    
    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            LOGGER.debug(str);
            LockSupport.unpark(nextThread);
        }
    }
}

结果:
19:07:54.031 [Thread-0] DEBUG com.multiThreads.test12.ParkUnpark - a
19:07:54.033 [Thread-1] DEBUG com.multiThreads.test12.ParkUnpark - b
19:07:54.033 [Thread-2] DEBUG com.multiThreads.test12.ParkUnpark - c
19:07:54.033 [Thread-0] DEBUG com.multiThreads.test12.ParkUnpark - a
19:07:54.033 [Thread-1] DEBUG com.multiThreads.test12.ParkUnpark - b
19:07:54.034 [Thread-2] DEBUG com.multiThreads.test12.ParkUnpark - c
19:07:54.034 [Thread-0] DEBUG com.multiThreads.test12.ParkUnpark - a
19:07:54.034 [Thread-1] DEBUG com.multiThreads.test12.ParkUnpark - b
19:07:54.034 [Thread-2] DEBUG com.multiThreads.test12.ParkUnpark - c
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值