Introduction to java concurrency (1) Shared model - Synchronized, Wait/Notify, pack/unpack

1. Shared model - monitor

1. Problems in sharing

1.1 Shared variable case

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.MTest1")
public class MTest1 {
    
    

    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
    
    

        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
                counter++;
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
                counter--;
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }
}

The result of the above may be 正数、负数、零. This is because the auto-increment and auto-decrement of static variables in Java are not atomic operations.

For example, for i++ (i is a static variable), the following JVM bytecode instructions will actually be generated:

getstatic i   // 获取静态变量i的值
iconst_1      // 准备常量1
iadd          // 自增
putstatic i   // 将修改后的值存入静态变量i

i – produces the following JVM bytecode instructions:

getstatic i      // 获取静态变量i的值
iconst_1         // 准备常量1
isub             // 自减
putstatic i      // 将修改后的值存入静态变量i

In Java, completing the self-increment and self-decrement of static variables requires data exchange between the main memory and the working memory.

insert image description here

如果是单线程以上 8 行代码是顺序执行(不会交错)没有问题:

insert image description here

但多线程下这 8 行代码可能交错运行,比如出现负数情况如下:

insert image description here

1.2 Critical section

There is no problem with a program running multiple threads. The problem lies in multiple threads accessing shared resources

In fact, there is no problem for
multiple threads to read shared resources . When multiple threads read and write shared resources, instruction interleaving occurs, and problems will arise. If there are multi-threaded read and write operations on shared resources in a code block , this code block is called a critical section**

static int counter = 0;

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

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

Multiple threads are executed in the critical section, and the results are unpredictable due to the different execution sequences of the code, which is called a race condition.

2. Use synchronized to solve the problems of sharing

In order to avoid the occurrence of race conditions in the critical section, there are many ways to achieve the goal.

  • Blocking solutions: synchronized, Lock

  • Non-blocking solution: atomic variables

Synchronized, that is, [object lock], it uses mutual exclusion so that at most one thread can hold the [object lock] at the same time, and other threads will be blocked when they want to acquire this [object lock]. This ensures that the thread holding the lock can safely execute the code in the critical section without worrying about thread context switching.

// 语法如下
synchronized(对象) // 线程1, 线程2(blocked)
{
    
    
	临界区
}

Appeal code that can be retrofitted:

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.MTest2")
public class MTest2 {
    
    

    static int counter = 0;
    static final Object room = new Object();

    public static void main(String[] args) throws InterruptedException {
    
    

        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
                synchronized (room){
    
    
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
                synchronized (room){
    
    
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }
}
  • synchronized(对象)The object in can be imagined as a room (room) with a unique entrance (door). The room can only be entered by one person at a time for calculation. Threads t1 and t2 are imagined as two people

  • When thread t1 is executed synchronized(room), it is as if t1 entered the room, locked the door and took the key, and executed count++the code inside the door

  • At this synchronized(room) time , it finds that the door is locked, so it can only wait outside the door, a context switch occurs, and it blocks

  • Even if the cpu time slice of t1 unfortunately runs out and is kicked out of the door (don’t misunderstand that the object can be executed forever if it is locked), the door is still locked at this time, t1 still holds the key, and t2 The thread is still in the blocked state and cannot enter. The door can only be opened when it is the next time t1 gets the time slice again.

  • When t1 finishes executing the code synchronized{} 块in , it will come out of the obj room and unlock the door, wake up the t2 thread and give him the key. Only then can the t2 thread enter the obj room, lock the door, take the key, and execute its count--code

如果把 synchronized(obj) 放在 for 循环的外面,如何理解?-- 原子性
如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?-- 锁对象
如果 t1 synchronized(obj) 而 t2 没有加会怎么样?如何理解?-- 锁对象

Using object-oriented thinking, put shared variables into a class, and transform the above code as follows:

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.MTest3")
public class MTest3 {
    
    


    public static void main(String[] args) throws InterruptedException {
    
    

        RoomCounter counter = new RoomCounter();


        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
                counter.increment();
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
                counter.decrement();
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }
}


class RoomCounter{
    
    

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

}

3. Synchronized on the method

// 普通方法
class Test{
    
    
    public synchronized void test() {
    
    
        
    }
}

// 等价于  此时锁住的是调用者
class Test{
    
    
    public void test() {
    
    
        synchronized(this) {
    
    
            
        }
    }
}
// 静态方法
class Test{
    
    
    public synchronized static void test() {
    
    
        
    }
}
// 等价于  此时锁住的是Test类
class Test{
    
    
    public static void test() {
    
    
        synchronized(Test.class) {
    
    
            
        }
    }
}

4. Variable security analysis

4.1 Basic concepts

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

  • thread safe if they are not shared

  • If they are shared, according to whether their status can be changed, there are two cases

    • Thread safe if only read operations

    • If there are read and write operations, this code is a critical section, and thread safety needs to be considered

package com.yyds.juc.monitor;

import java.util.ArrayList;

public class MTest4ThreadUnsafe {
    
    

    ArrayList<String> list = new ArrayList<>();

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
    
    
        // 如果线程2 还未 add,线程1 remove 就会报错
        MTest4ThreadUnsafe test = new MTest4ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
    
            new Thread(() -> {
    
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
    
    public void method1(int loopNumber){
    
    
        for (int i = 0; i < loopNumber; i++) {
    
    
            // 临界区,会产生竞态条件
            method2();
            method3();
        }
    }

    private void method2() {
    
    
        list.add("1");
    }
    private void method3() {
    
    
        list.remove(0);
    }

}

Change the member variable to a local variable, no error will be reported, the code is as follows

package com.yyds.juc.monitor;

import java.util.ArrayList;

public class MTest5ThreadSafe {
    
    

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
    
    
        // list 是局部变量,每个线程调用时会创建其不同实例,没有共享
        // 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
        // method3 的参数分析与 method2 相同
        MTest4ThreadUnsafe test = new MTest4ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
    
            new Thread(() -> {
    
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }

    public void method1(int loopNumber){
    
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
    
            // 临界区,会产生竞态条件
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
    
    
        list.add("1");
    }
    private void method3(ArrayList<String> list) {
    
    
        list.remove(0);
    }
}

局部变量是否线程安全?

  • Local variables are thread-safe

  • But the object referenced by the local variable may not be

    • If the object is not accessed by the effect of the escape method, it is thread-safe

    • If the object escapes the scope of the method, thread safety needs to be considered

// 局部变量是线程安全的
public static void test1() {
    
    
    int i = 10;
    i++;
}
// 如下图,每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享

insert image description here

In the above example, if the member variable is changed to a local variable, no error will be reported, but if the following appears 对象逃离方法的作用范围情况, the error will still be reported

package com.yyds.juc.monitor;

import java.util.ArrayList;

public class MTest5ThreadSafe {
    
    

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
    
    
        // list 是局部变量,每个线程调用时会创建其不同实例,没有共享
        // 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象
        // method3 的参数分析与 method2 相同
        MTest4ThreadUnsafe test = new MTest4ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
    
            new Thread(() -> {
    
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }

    public void method1(int loopNumber){
    
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
    
            // 临界区,会产生竞态条件
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
    
    
        list.add("1");
    }
    private void method3(ArrayList<String> list) {
    
    
        list.remove(0);
    }
}


class ThreadSafeSubClass extends MTest5ThreadSafe{
    
    

    public void method3(ArrayList<String> list) {
    
    
        new Thread(() -> {
    
    
            list.remove(0);
        }).start();
    }
}

常见线程安全类
String
Integer
StringBuffer
Random
Vector
Hashtable
Classes under the java.util.concurrent package
Here, they are thread-safe, which means that when multiple threads call a method of the same instance, they are thread-safe.

Each of their methods is atomic, but note that combinations of their methods are not atomic.

// 如下,多个方法的组合不是原子

Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
    
    
	table.put("key", value);
}

insert image description here

4.2 Detailed explanation of the case

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j(topic = "c.MTest7ExerciseTransfer")
public class MTest7ExerciseTransfer {
    
    
    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);
        }
    }
}


// 运行结果
10:08:56.480 c.MTest7ExerciseTransfer [main] - total:4267
    
    
//使用synchronized进行改进
 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) {
    
    
        synchronized (Account.class){
    
     // 锁是Account类
            if (this.money > amount) {
    
    
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}
// 运行结果
10:10:38.624 c.MTest7ExerciseTransfer [main] - total:2000

5、Monitor

5.1 Object header

# 普通对象
|--------------------------------------------------------------|
|                    Object Header (64 bits)                   |
|------------------------------------|-------------------------|
|         Mark Word (32 bits)        |    Klass Word (32 bits) |
|------------------------------------|-------------------------|


# 其中 Mark Word 结构为(32位虚拟机)
|-------------------------------------------------------|--------------------|
|                      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 (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   |
|--------------------------------------------------------------------|--------------------|

5.2 Monitor (lock)

​ Each Java object can be associated with a Monitor object. If synchronized is used to lock the object (heavyweight), the Mark Word of the object header will be set to a pointer to the Monitor object.

insert image description here

  • At the beginning, Owner in Monitor is null

  • When Thread-2 executes synchronized(obj), the Owner of the Monitor will be set to Thread-2, and there can only be one Owner in the Monitor

  • In the process of Thread-2 locking, if Thread-3, Thread-4, Thread-5 also execute synchronized(obj), it will enter EntryList BLOCKED

  • Thread-2 executes the content of the synchronization code block, and then wakes up the waiting threads in the EntryList to compete for the lock. The competition is unfair

  • Thread-0 and Thread-1 in the WaitSet in the figure are threads that have acquired the lock before, but the conditions are not satisfied and enter the WAITING state

5.3 Principle of Synchronization

static final Object lock = new Object();
static int counter = 0;

public static void main(String[] args) {
    
    
    synchronized (lock) {
    
    
    	counter++;
    }
}
Code:
	stack=2, locals=3, args_size=1
        0: getstatic #2     // <- lock引用 (synchronized开始)
        3: dup
        4: astore_1         // lock引用 -> slot 1
        5: monitorenter     // 将 lock对象 MarkWord 置为 Monitor 指针
        6: getstatic #3     // <- i
        9: iconst_1         // 准备常数 1
        10: iadd            // +1
        11: putstatic #3    // -> i
        14: aload_1         // <- lock引用
        15: monitorexit     // 将 lock对象 MarkWord 重置, 唤醒 EntryList
        16: goto 24
        19: astore_2        // e -> slot 2
        20: aload_1         // <- lock引用
        21: monitorexit     // 将 lock对象 MarkWord 重置, 唤醒 EntryList
        22: aload_2         // <- slot 2 (e)
        23: athrow          // throw e
        24: return
	Exception table:
        from to target type
        6  16 19 any
        19 22 19 any

5.4 Advanced Synchronized

5.4.1 Lightweight locks

Scenarios for using lightweight locks: If an object needs to be locked by multiple threads, 加锁的时间是错开的(也就是没有竞争)it can be optimized using lightweight locks.
Lightweight locks are transparent to users, that is, the syntax is still synchronized.

static final Object obj = new Object();

public static void method1() {
    
    
    synchronized( obj ) {
    
    
    	// 同步块 A
    	method2();
    }
}
public static void method2() {
    
    
    synchronized( obj ) {
    
    
    	// 同步块 B
    }
}

insert image description here

1. Create a lock record (Lock Record) object. The stack frame of each thread will contain a lock record structure, which can store the Mark Word of the locked object

2. Make the Object reference in the lock record point to the lock object, and try to replace the Mark Word of the Object with cas, and store the value of the Mark Word into the lock record

​ If the cas replacement is successful, it is stored in the object header 锁记录地址和状态 00, indicating that the thread locks the object (as shown below)

insert image description here

​ If cas fails, there are two cases
: If other threads already hold the lightweight lock of the Object, it indicates that there is competition, enter锁膨胀过程

​ If you execute it yourself synchronized 锁重入, then 再添加一条 Lock Record 作为重入的计数(as shown below)

insert image description here

3. When exiting the synchronized code block (unlocking), if there is a lock record with a value of null, it means that there is re-entry. At this time, the lock record is reset, which means that the re-entry count is reduced by one

4. When exiting the synchronized code block (unlocking), the value of the lock record is not null. At this time, the use of cas will Mark Word 的值恢复给对象头
succeed and the unlocking will
fail. It means that the lightweight lock has been carried out 锁膨胀或已经升级为重量级锁, and the process of unlocking the heavyweight lock is entered.

5.4.2 Lock Expansion

If the CAS operation fails during the process of trying to add a lightweight lock, then 一种情况就是有其它线程为此对象加上了轻量级锁(有竞争)lock expansion is required to change the lightweight lock into a heavyweight lock.

1. When Thread-1 performs lightweight locking, Thread-0 has already added a lightweight lock to the object

insert image description here

2. At this time, Thread-1 fails to add a lightweight lock, and enters the lock expansion process
to apply for a Monitor lock for the Object object, 让 Object 指向重量级锁地址,然后自己进入 Monitor 的 EntryList BLOCKED

insert image description here

3. When Thread-0 exits the synchronization block to unlock, use cas to restore the value of Mark Word to the object header, and fail. At this time, it will enter the heavyweight unlocking process.即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程 。

5.4.3 Spin optimization

When heavyweight locks compete, spin can also be used for optimization. If the current thread spins successfully (that is, the lock-holding thread has exited the synchronization block and released the lock), the current thread can avoid blocking.

Notice:

  • Spinning will take up CPU time, single-core CPU spinning is a waste, and multi-core CPU spinning can give full play to its advantages.

  • After Java 6, the spin lock is adaptive. For example, if the object has just succeeded in a spin operation, then it is considered that the possibility of success of this spin is high, so it will spin more times; otherwise, it will spin less or even Does not spin.

  • After Java 7, it is not possible to control whether to enable the spin function.

5.4.4 Bias lock

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

insert image description here

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

Biased locking was introduced in Java 6 as a further optimization: only the first use CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS.

insert image description here

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

1. The hashCode of the object is called, and 但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销
the lightweight lock will record the hashCode in the lock record.
The heavyweight lock will record the hashCode in the Monitor

2. When other threads use the biased lock object, the biased lock will be upgraded to a lightweight lock.

3. Call wait/notify

5.4.2 Batch rebiasing

If the object is accessed by multiple threads, but there is no competition, then the object that is biased to thread T1 still has the opportunity to re-bias T2, and the re-biasing will reset the Thread ID of the object
. I think, am I biased wrong, so I will re-bias to the locking thread when locking these objects

5.4.3 Batch undo

When the threshold of undoing the biased lock exceeds 40 times, the jvm will feel that it is indeed biased wrong, and it should not be biased at all. Then all objects of the entire class will become unbiasable, and newly created objects will also be unbiasable.

5.4.5 Lock Elimination

@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 // JIT即时编译器会进行锁消除优化
    public void b() throws Exception {
    
    
    Object o = new Object();
        synchronized (o) {
    
    
       	 x++;
        }
    }
}

6、Wait/Notify

6.1 Brief introduction of Wait/Notify

  • obj.wait() Let the thread entering the object monitor wait in waitSet

  • 挑一个obj.notify() wakes up in a thread that is waiting on waitSet on object

  • obj.notifyAll() wakes up all threads waiting in waitSet on the object

Note: They are all means of cooperation between threads, and they all belong to the methods of the Object object. 必须获得此对象的锁, to call these methods.

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;

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

        // 主线程两秒后执行
        sleep(2000);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
    
    
            obj.notify(); // 唤醒obj上一个线程
           // obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}
# notify 的一种结果
14:27:24.172 c.MTest8 [t1] - 执行....
14:27:24.188 c.MTest8 [t2] - 执行....
14:27:26.182 c.MTest8 [main] - 唤醒 obj 上其它线程
14:27:26.182 c.MTest8 [t1] - 其它代码....

# notifyAll 的结果
14:25:12.212 c.MTest8 [t1] - 执行....
14:25:12.212 c.MTest8 [t2] - 执行....
14:25:14.214 c.MTest8 [main] - 唤醒 obj 上其它线程
14:25:14.214 c.MTest8 [t2] - 其它代码....
14:25:14.214 c.MTest8 [t1] - 其它代码....

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

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

  1. sleep is a Thread method, and wait is an Object method

  2. sleep does not need to be used in conjunction with synchronized, butwait 需要和 synchronized 一起用

  3. sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁

  4. They are TIMED_WAITING

6.2 How to use Wait/Notify correctly

6.2.1 Original code

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.MTest9")
public class MTest9 {
    
    
    static final  Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
    
    

        new Thread(() -> {
    
    
            synchronized (room){
    
    
                log.debug("有烟没?【{}】",hasCigarette);

                if(!hasCigarette){
    
    
                    log.debug("没有烟,先休息一会!");
                    try {
    
    
                        sleep(2000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }

                log.debug("有烟没?【{}】",hasCigarette);
                if(hasCigarette){
    
    
                    log.debug("可以干活了!");
                }
            }
        },"tom").start();


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

        sleep(1000);
        new Thread(() -> {
    
    
            // 这里能不能加 synchronized (room)?
            hasCigarette = true;
            log.debug("烟到了噢!");
        }, "送烟的").start();
    }
}
14:48:48.419 c.MTest9 [tom] - 有烟没?【false】
14:48:48.419 c.MTest9 [tom] - 没有烟,先休息一会!
14:48:49.422 c.MTest9 [送烟的] - 烟到了噢!
14:48:50.421 c.MTest9 [tom] - 有烟没?【true】
14:48:50.421 c.MTest9 [tom] - 可以干活了!
14:48:50.421 c.MTest9 [其它人] - 可以开始干活了
14:48:50.421 c.MTest9 [其它人] - 可以开始干活了
14:48:50.421 c.MTest9 [其它人] - 可以开始干活了
14:48:50.421 c.MTest9 [其它人] - 可以开始干活了
14:48:50.421 c.MTest9 [其它人] - 可以开始干活了

# 其它干活的线程,都要一直阻塞,效率太低
# tom线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
# 加了 synchronized (room) 后,就好比tom在里面反锁了门睡觉,烟根本没法送进门,main 没加synchronized 就好像 main 线程是翻窗户进来的

# 下面使用 wait - notify 机制进行改进

6.2.2 Improved Code - Version 2

package com.yyds.juc.monitor;

import static java.lang.Thread.sleep;

import lombok.extern.slf4j.Slf4j;


@Slf4j(topic = "c.MTest9V2")
public class MTest9V2 {
    
    
    static final  Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
    
    

        new Thread(() -> {
    
    
            synchronized (room){
    
    
                log.debug("有烟没?【{}】",hasCigarette);

                if(!hasCigarette){
    
    
                    log.debug("没有烟,先休息一会!");
                    try {
    
    
                        room.wait(2000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }

                log.debug("有烟没?【{}】",hasCigarette);
                if(hasCigarette){
    
    
                    log.debug("可以干活了!");
                }
            }
        },"tom").start();


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

        sleep(1000);
        new Thread(() -> {
    
    
            synchronized (room) {
    
     
                hasCigarette = true;
                log.debug("烟到了噢!");
                // 唤醒
                room.notify();
            }

        }, "送烟的").start();
    }
}

14:58:59.780 c.MTest9V2 [tom] - 有烟没?【false】
14:58:59.780 c.MTest9V2 [tom] - 没有烟,先休息一会!
14:58:59.780 c.MTest9V2 [其它人] - 可以开始干活了
14:58:59.780 c.MTest9V2 [其它人] - 可以开始干活了
14:58:59.780 c.MTest9V2 [其它人] - 可以开始干活了
14:58:59.780 c.MTest9V2 [其它人] - 可以开始干活了
14:58:59.795 c.MTest9V2 [其它人] - 可以开始干活了
14:59:00.782 c.MTest9V2 [送烟的] - 烟到了噢!
14:59:00.782 c.MTest9V2 [tom] - 有烟没?【true】
14:59:00.782 c.MTest9V2 [tom] - 可以干活了!


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

6.2.3 Improved Code - Version 3

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;


@Slf4j(topic = "c.MTest9V3")
public class MTest9V3 {
    
    
    static final  Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
    
    

        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("没干成活...");
                }
            }
        },"tom").start();


        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("没干成活...");
                }
            }
        },"jerry").start();


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

        sleep(1000);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasTakeout = true;
                log.debug("外卖到了噢!");
                // 唤醒(随机唤醒一个)
                room.notify();
            }

        }, "送外卖的").start();
    }
}
15:06:23.726 c.MTest9V3 [tom] - 有烟没?【false】
15:06:23.726 c.MTest9V3 [tom] - 没有烟,先休息一会!
15:06:23.726 c.MTest9V3 [其它人] - 可以开始干活了
15:06:23.726 c.MTest9V3 [其它人] - 可以开始干活了
15:06:23.726 c.MTest9V3 [其它人] - 可以开始干活了
15:06:23.726 c.MTest9V3 [其它人] - 可以开始干活了
15:06:23.726 c.MTest9V3 [其它人] - 可以开始干活了
15:06:23.726 c.MTest9V3 [jerry] - 有外卖没?【false】
15:06:23.726 c.MTest9V3 [jerry] - 没有外卖,先休息一会!
15:06:24.741 c.MTest9V3 [送外卖的] - 外卖到了噢!
15:06:24.741 c.MTest9V3 [tom] - 有烟没?【false】
15:06:24.741 c.MTest9V3 [tom] - 没干成活...

# notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】
# 解决方法,改为 notifyAll

6.2.3 Improved Code - Version 4

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;


@Slf4j(topic = "c.MTest9V4")
public class MTest9V4 {
    
    
    static final  Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
    
    

        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("没干成活...");
                }
            }
        },"tom").start();


        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("没干成活...");
                }
            }
        },"jerry").start();


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

        sleep(1000);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasTakeout = true;
                log.debug("外卖到了噢!");
                // 全部唤醒
                room.notifyAll();
            }

        }, "送外卖的").start();
    }
}
15:09:32.757 c.MTest9V4 [tom] - 有烟没?【false】
15:09:32.757 c.MTest9V4 [tom] - 没有烟,先休息一会!
15:09:32.757 c.MTest9V4 [其它人] - 可以开始干活了
15:09:32.757 c.MTest9V4 [其它人] - 可以开始干活了
15:09:32.757 c.MTest9V4 [其它人] - 可以开始干活了
15:09:32.757 c.MTest9V4 [其它人] - 可以开始干活了
15:09:32.757 c.MTest9V4 [其它人] - 可以开始干活了
15:09:32.757 c.MTest9V4 [jerry] - 有外卖没?【false】
15:09:32.757 c.MTest9V4 [jerry] - 没有外卖,先休息一会!
15:09:33.759 c.MTest9V4 [送外卖的] - 外卖到了噢!
15:09:33.759 c.MTest9V4 [jerry] - 有外卖没?【true】
15:09:33.759 c.MTest9V4 [jerry] - 可以干活了!
15:09:33.759 c.MTest9V4 [tom] - 有烟没?【false】
15:09:33.759 c.MTest9V4 [tom] - 没干成活...

# 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了
# 解决方法,用 while + wait,当条件不成立,再次 wait

6.2.3 Improved Code - Version 5

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;


@Slf4j(topic = "c.MTest9V5")
public class MTest9V5 {
    
    
    static final  Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
    
    

        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("没干成活...");
                }
            }
        },"tom").start();


        new Thread(() -> {
    
    
            synchronized (room){
    
    
                log.debug("有外卖没?【{}】",hasTakeout);

                while(!hasTakeout){
    
    
                    log.debug("没有外卖,先休息一会!");
                    try {
    
    
                        room.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }

                log.debug("有外卖没?【{}】",hasTakeout);
                if(hasTakeout){
    
    
                    log.debug("可以干活了!");
                }else {
    
    
                    log.debug("没干成活...");
                }
            }
        },"jerry").start();


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

        sleep(1000);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasTakeout = true;
                log.debug("外卖到了噢!");
                // 全部唤醒
                room.notifyAll();
            }

        }, "送外卖的").start();
    }
}
15:13:19.224 c.MTest9V5 [tom] - 有烟没?【false】
15:13:19.224 c.MTest9V5 [tom] - 没有烟,先休息一会!
15:13:19.224 c.MTest9V5 [其它人] - 可以开始干活了
15:13:19.224 c.MTest9V5 [其它人] - 可以开始干活了
15:13:19.224 c.MTest9V5 [其它人] - 可以开始干活了
15:13:19.224 c.MTest9V5 [其它人] - 可以开始干活了
15:13:19.224 c.MTest9V5 [其它人] - 可以开始干活了
15:13:19.224 c.MTest9V5 [jerry] - 有外卖没?【false】
15:13:19.224 c.MTest9V5 [jerry] - 没有外卖,先休息一会!
15:13:20.224 c.MTest9V5 [送外卖的] - 外卖到了噢!
15:13:20.224 c.MTest9V5 [jerry] - 有外卖没?【true】
15:13:20.224 c.MTest9V5 [jerry] - 可以干活了!
15:13:20.224 c.MTest9V5 [tom] - 没有烟,先休息一会!

To summarize: the template is as follows

synchronized(lock) {
    
    
    while(条件不成立) {
    
    
    	lock.wait();
    }
	// 干活
}

//另一个线程
synchronized(lock) {
    
    
    lock.notifyAll();
}

6.3 Protective Pause

6.3.1 Basic concepts

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

  • There is a result that needs to be passed from one thread to another, let them associate the same GuardedObject

  • If you have results constantly passing from one thread to another then you can use a message queue (see Producer/Consumer)

  • In JDK, the implementation of join and the implementation of Future adopt this mode

  • Because it is waiting for the result of the other party, it is classified into synchronous mode

insert image description here

6.3.2 Simple implementation

package com.yyds.juc.monitor;

public class GuardedObject {
    
    

    private Object response;
    private final Object lock = new Object();
    /**
     * 获取response
     */
    public Object get(){
    
    
        synchronized (lock){
    
    
            // 条件不满足,就等待
            while (response == null){
    
    
                try {
    
    
                    lock.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    /**
     * 将数据获取给response
     */
    public void complete(Object response){
    
    
        synchronized (lock){
    
    
            // 条件满足,通知等待线程
            this.response = response;
            lock.notifyAll();
        }
    }
}
package com.yyds.juc.monitor;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class Downloader {
    
    
    public static List<String> download() throws IOException {
    
    
        HttpURLConnection conn = (HttpURLConnection) new URL("https://www.baidu.com/").openConnection();
        List<String> lines = new ArrayList<>();
        try (BufferedReader reader =
                     new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
    
    
            String line;
            while ((line = reader.readLine()) != null) {
    
    
                lines.add(line);
            }
        }
        return lines;
    }
}
package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.List;


@Slf4j(topic = "c.MTest0")
public class MTest0 {
    
    

    public static void main(String[] args) {
    
    

        GuardedObject guardedObject = new GuardedObject();

        // t2线程执行下载
        new Thread(() -> {
    
    
            List<String> response = null;
            try {
    
    
                response = Downloader.download();
                log.debug("download complete......");
                guardedObject.complete(response);
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }

        },"t2").start();


        // t1线程获取t2线程的结果
        new Thread(() -> {
    
    
            log.debug("waiting...");
            Object response = guardedObject.get();
            log.debug("get response: [{}] lines", ((List<String>) response).size());
        },"t1").start();
    }
}
18:10:24.053 c.MTest0 [t1] - waiting...
18:10:24.977 c.MTest0 [t2] - download complete......
18:10:24.977 c.MTest0 [t1] - get response: [3] lines

6.3.3 Implementation with timeout version

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.GuardedObject2")
public class GuardedObject2 {
    
    

    private Object response;
    private final Object lock = new Object();


    /**
     * 获取response
     */
    public Object get(long millis){
    
    
        synchronized (lock){
    
    

            // 1、记录最初的时间
            long begin = System.currentTimeMillis();
            // 2、已经经历的时间
            long timePassed = 0;

            // 条件不满足,就等待
            while (response == null){
    
    

                // 3、还需要等待的时间
                long waitTime = millis - timePassed;
                log.debug("waitTime: {}", waitTime);

                if(waitTime <= 0){
    
    
                    log.debug("break...");
                    break;
                }

                try {
    
    
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                // 4、如果提前被唤醒
                timePassed = System.currentTimeMillis() - begin;
                log.debug("timePassed: {}, object is null 【{}】", timePassed, response == null);
            }
            return response;
        }
    }

    /**
     * 将数据获取给response
     */
    public void complete(Object response){
    
    
        synchronized (lock){
    
    
            // 条件满足,通知等待线程
            this.response = response;
            log.debug("notify...");
            lock.notifyAll();
        }
    }
}

test

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.MTest0Time")
public class MTest0Time {
    
    
    public static void main(String[] args) {
    
    
        GuardedObject2 guardedObject2 = new GuardedObject2();

        // t2线程结果传输给t1
        new Thread(() -> {
    
    
            try {
    
    
                sleep(1000);
                guardedObject2.complete(null);
                sleep(1000);
                guardedObject2.complete(Arrays.asList("hello","world"));
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        },"t2").start();


        Object response = guardedObject2.get(2500);
        if(response != null){
    
    
            log.debug("get response: [{}] lines", ((List<String>) response).size());
        }else {
    
    
            log.debug("can't get response");
        }
    }
}

// 测试结果如下
10:31:22.105 c.GuardedObject2 [main] - waitTime: 2500
10:31:23.119 c.GuardedObject2 [t2] - notify...
10:31:23.119 c.GuardedObject2 [main] - timePassed: 1014, object is nulltrue10:31:23.119 c.GuardedObject2 [main] - waitTime: 1486
10:31:24.121 c.GuardedObject2 [t2] - notify...
10:31:24.121 c.GuardedObject2 [main] - timePassed: 2016, object is nullfalse10:31:24.121 c.MTest0Time [main] - get response: [2] lines

6.3.4 join principle

The embodiment of join is a protective pause.

t1.join();



// 源码如下
public final void join() throws InterruptedException {
    
    
        join(0);
}


 public final synchronized void join(long millis) throws InterruptedException {
    
    
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
    
    
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
    
    
            // 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束
            while (isAlive()) {
    
    
                wait(0);
            }
        } else {
    
    
            while (isAlive()) {
    
    
                long delay = millis - now;
                if (delay <= 0) {
    
    
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
}

6.3.5 Multitasking GuardedObject

​ In the figure, Futures are like mailboxes on the first floor of a residential building (each mailbox has a room number), t0, t2, and t4 on the left are like residents waiting for mail, and t1, t3, and t5 on the right are like postmen.
​ If you need to use GuardedObject objects between multiple classes, it is not very convenient to pass them as parameters, so design an intermediate class for decoupling, which can not only decouple [result waiter] and [result producer], but also Supports the management of multiple tasks at the same time.

insert image description here

package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.GuardedObject3")
public class GuardedObject3 {
    
    

    // 新增 id 用来标识 Guarded Object
    private int id;

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

    public int getId() {
    
    
        return id;
    }

    private Object response;

    /**
     * 获取response
     */
    public Object get(long millis){
    
    
        synchronized (this){
    
    

            // 1、记录最初的时间
            long begin = System.currentTimeMillis();
            // 2、已经经历的时间
            long timePassed = 0;

            // 条件不满足,就等待
            while (response == null){
    
    

                // 3、还需要等待的时间
                long waitTime = millis - timePassed;
                log.debug("waitTime: {}", waitTime);

                if(waitTime <= 0){
    
    
                    log.debug("break...");
                    break;
                }

                try {
    
    
                    this.wait(waitTime);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                // 4、如果提前被唤醒
                timePassed = System.currentTimeMillis() - begin;
                log.debug("timePassed: {}, object is null 【{}】", timePassed, response == null);
            }
            return response;
        }
    }

    /**
     * 将数据获取给response
     */
    public void complete(Object response){
    
    
        synchronized (this){
    
    
            // 条件满足,通知等待线程
            this.response = response;
            log.debug("notify...");
            this.notifyAll();
        }
    }
}
package com.yyds.juc.monitor;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

/**
 * 中间解耦的类
 */
public class MailBoxes {
    
    

    private static Map<Integer, GuardedObject3> boxes = new Hashtable<>();

    private static int id = 1;

    // 产生唯一 id
    private static synchronized int generateId() {
    
    
        return id++;
    }

    public static GuardedObject3 getGuardedObject(int id) {
    
    
        return boxes.remove(id);
    }

    public static GuardedObject3 createGuardedObject() {
    
    
        GuardedObject3 go = new GuardedObject3(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
    
    
        return boxes.keySet();
    }
}
package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.MPeople")
public class MPeople extends Thread {
    
    

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

}



package com.yyds.juc.monitor;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.MPostman")
public class MPostman extends Thread {
    
    
    private int id;
    private String mail;
    public MPostman(int id, String mail) {
    
    
        this.id = id;
        this.mail = mail;
    }
    @Override
    public void run() {
    
    
        GuardedObject3 guardedObject = MailBoxes.getGuardedObject(id);
        log.debug("送信 id:{}, 内容:{}", id, mail);
        guardedObject.complete(mail);
    }
}

test class

package com.yyds.juc.monitor;

import static java.lang.Thread.sleep;

public class MuliGuadeTest {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < 3; i++) {
    
    
            new MPeople().start();
        }

        sleep(1000);

        for (Integer id : MailBoxes.getIds()) {
    
    
            new MPostman(id, "内容" + id).start();
        }
    }
}

// 测试结果如下
11:20:37.183 c.MPeople [Thread-0] - 开始收信 id:1
11:20:37.183 c.MPeople [Thread-2] - 开始收信 id:2
11:20:37.183 c.MPeople [Thread-1] - 开始收信 id:3
11:20:37.186 c.GuardedObject3 [Thread-2] - waitTime: 5000
11:20:37.186 c.GuardedObject3 [Thread-0] - waitTime: 5000
11:20:37.186 c.GuardedObject3 [Thread-1] - waitTime: 5000
11:20:38.195 c.MPostman [Thread-3] - 送信 id:3, 内容:内容3
11:20:38.195 c.MPostman [Thread-4] - 送信 id:2, 内容:内容2
11:20:38.195 c.GuardedObject3 [Thread-3] - notify...
11:20:38.195 c.GuardedObject3 [Thread-4] - notify...
11:20:38.195 c.MPostman [Thread-5] - 送信 id:1, 内容:内容1
11:20:38.195 c.GuardedObject3 [Thread-2] - timePassed: 1009, object is nullfalse11:20:38.195 c.GuardedObject3 [Thread-5] - notify...
11:20:38.195 c.GuardedObject3 [Thread-1] - timePassed: 1009, object is nullfalse11:20:38.195 c.MPeople [Thread-2] - 收到信 id:2, 内容:内容2
11:20:38.195 c.MPeople [Thread-1] - 收到信 id:3, 内容:内容3
11:20:38.195 c.GuardedObject3 [Thread-0] - timePassed: 1009, object is nullfalse11:20:38.195 c.MPeople [Thread-0] - 收到信 id:1, 内容:内容1

6.4 Producer/Consumer

  • Unlike the GuardObject in the previous protective suspension, there is no need for one-to-one correspondence between the threads that produce results and consume results

  • The consumption queue can be used to balance the thread resources of production and consumption

  • The producer is only responsible for generating the result data, and does not care how the data is processed, while the consumer concentrates on processing the result data

  • The message queue has a capacity limit, no more data will be added when it is full, and no more data will be consumed when it is empty

  • Various blocking queues in JDK adopt this mode

insert image description here

The code is implemented as follows

package com.yyds.juc.produce;

public class Message {
    
    
    private int id;
    private Object message;


    public Message(int id, Object message) {
    
    
        this.id = id;
        this.message = message;
    }
    public int getId() {
    
    
        return id;
    }

    public Object getMessage() {
    
    
        return message;
    }
}
package com.yyds.juc.produce;

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;

@Slf4j(topic = "c.MessageQueue")
public class MessageQueue {
    
    

    private LinkedList<Message> queue;

    private int capacity;

    public MessageQueue(int capacity) {
    
    
        this.capacity = capacity;
        this.queue = new LinkedList<>();
    }

    public Message take() {
    
    
        synchronized (queue) {
    
    
            while (queue.isEmpty()) {
    
    
                log.debug("没货了, wait");
                try {
    
    
                    queue.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            Message message = queue.removeFirst();
            queue.notifyAll();
            return message;
        }
    }

    public void put(Message message) {
    
    
        synchronized (queue) {
    
    
            while (queue.size() == capacity) {
    
    
                log.debug("库存已达上限, wait");
                try {
    
    
                    queue.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            queue.addLast(message);
            queue.notifyAll();
        }
    }

}
package com.yyds.juc.produce;

import com.yyds.juc.monitor.Downloader;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.List;

@Slf4j(topic = "c.ProConTest")
public class ProConTest {
    
    
    public static void main(String[] args) {
    
    
        // 创建容量为2的队列
        MessageQueue messageQueue = new MessageQueue(2);

        // 4 个生产者线程, 下载任务
        for (int i = 0; i < 4; i++) {
    
    
            int id = i;
            new Thread(() -> {
    
    
                try {
    
    
                    log.debug("download...");
                    List<String> response = Downloader.download();
                    log.debug("try put message({})", id);
                    messageQueue.put(new Message(id, response));
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }, "生产者" + i).start();
        }


        // 1 个消费者线程, 处理结果
        new Thread(() -> {
    
    
            while (true) {
    
    
                Message message = messageQueue.take();
                List<String> response = (List<String>) message.getMessage();
                log.debug("take message({}): [{}] lines", message.getId(), response.size());
            }
        }, "消费者").start();
        
    }
}
// 测试结果如下
11:39:46.911 c.ProConTest [生产者0] - download...
11:39:46.911 c.MessageQueue [消费者] - 没货了, wait
11:39:46.911 c.ProConTest [生产者3] - download...
11:39:46.911 c.ProConTest [生产者1] - download...
11:39:46.911 c.ProConTest [生产者2] - download...
11:39:47.853 c.ProConTest [生产者2] - try put message(2)
11:39:47.853 c.ProConTest [生产者1] - try put message(1)
11:39:47.853 c.ProConTest [生产者3] - try put message(3)
11:39:47.853 c.ProConTest [生产者0] - try put message(0)
11:39:47.855 c.MessageQueue [生产者0] - 库存已达上限, wait
11:39:47.855 c.MessageQueue [生产者3] - 库存已达上限, wait
11:39:47.855 c.ProConTest [消费者] - take message(2): [3] lines
11:39:47.855 c.MessageQueue [生产者0] - 库存已达上限, wait
11:39:47.855 c.ProConTest [消费者] - take message(1): [3] lines
11:39:47.856 c.ProConTest [消费者] - take message(3): [3] lines
11:39:47.856 c.ProConTest [消费者] - take message(0): [3] lines
11:39:47.856 c.MessageQueue [消费者] - 没货了, wait

7、pack、unpack

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

// 恢复某个线程的运行
LockSupport.unpark(暂停线程对象)

Compared with Object's wait & notify

  • wait, notify and notifyAll must be used together with Object Monitor, but park and unpark do not have to

  • park & ​​unpark is to [block] and [wake up] threads in units of threads, while notify can only randomly wake up one waiting thread, and notifyAll wakes up all waiting threads, which is not so [precise]

  • park & ​​unpark can unpark first, but wait & notify cannot notify first

7.1 Pack first and then unpack

insert image description here

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

insert image description here

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

7.2 First unpack and then pack

insert image description here

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

8. Six states of threads

insert image description here

Case 1 NEW --> RUNNABLE

When calling the t.start() method, by NEW --> RUNNABLE

Case 2 RUNNABLE <–> WAITING

After the t thread acquires the object lock with synchronized(obj)

  • When the obj.wait() method is called, the t thread goes from RUNNABLE --> WAITING

  • When calling obj.notify(), obj.notifyAll(), t.interrupt()

    • The competition lock is successful, and the t thread is from WAITING --> RUNNABLE

    • Competitive lock failure, t thread from WAITING --> BLOCKED

Case 3 RUNNABLE <–> WAITING

  • When the current thread calls the t.join() method, the current thread starts from RUNNABLE --> WAITING

    • Note that the current thread is waiting on the monitor of the t thread object
  • t When the thread finishes running, or when interrupt() of the current thread is called, the current thread starts from WAITING --> RUNNABLE

Case 4 RUNNABLE <–> WAITING

  • The current thread calls the LockSupport.park() method to make the current thread from RUNNABLE --> WAITING

  • Calling LockSupport.unpark (target thread) or calling interrupt() of the thread will make the target thread from WAITING -->
    RUNNABLE

Case 5 RUNNABLE <–> TIMED_WAITING

After the t thread acquires the object lock with synchronized(obj)

  • When the obj.wait(long n) method is called, the t thread starts from RUNNABLE --> TIMED_WAITING

  • t thread waits for more than n milliseconds, or calls obj.notify(), obj.notifyAll(), t.interrupt()

    • The competition lock is successful, and the t thread starts from TIMED_WAITING --> RUNNABLE

    • Competitive lock failure, t thread from TIMED_WAITING --> BLOCKED

Case 6 RUNNABLE <–> TIMED_WAITING

  • When the current thread calls the t.join(long n) method, the current thread starts from RUNNABLE --> TIMED_WAITING

    • Note that the current thread is waiting on the monitor of the t thread object
  • When the current thread waits for more than n milliseconds, or the t thread ends, or calls the interrupt() of the current thread, the current thread changes from
    TIMED_WAITING --> RUNNABLE

Case 7 RUNNABLE <–> TIMED_WAITING

  • The current thread calls Thread.sleep(long n), and the current thread starts from RUNNABLE --> TIMED_WAITING

  • The current thread wait time exceeds n milliseconds, and the current thread starts from TIMED_WAITING --> RUNNABLE

Case 8 RUNNABLE <–> TIMED_WAITING

  • When the current thread calls LockSupport.parkNanos(long nanos) or LockSupport.parkUntil(long millis), the current thread
    starts from RUNNABLE --> TIMED_WAITING

  • Calling LockSupport.unpark (target thread) or calling interrupt() of the thread, or waiting for a timeout, will make the target thread change from
    TIMED_WAITING–> RUNNABLE

Case 9 RUNNABLE <–> BLOCKED

  • If the competition fails when the t thread acquires the object lock with synchronized(obj), it will change from RUNNABLE --> BLOCKED

  • After the execution of the synchronization code block of the thread holding the obj lock is completed, all BLOCKED threads on the object will be awakened to re-compete. If the t thread competition succeeds,
    from BLOCKED --> RUNNABLE, other failed threads are still BLOCKED

Case 10 RUNNABLE <–> TERMINATED

All the codes of the current thread have finished running and enter TERMINATED

9. Deadlock, livelock, starvation

9.1 Deadlock

A thread needs to acquire multiple locks at the same time, and deadlocks are prone to occur at this time. The following code produces a deadlock.

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;
import static java.lang.Thread.sleep;

@Slf4j(topic = "c.DeadLockTest")
public class DeadLockTest {
    
    
    public static void main(String[] args) {
    
    

        Object A = new Object();
        Object B = new Object();

        Thread t1 = new Thread(() -> {
    
    
            synchronized (A) {
    
    
                log.debug("lock A");
                try {
    
    
                    sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (B) {
    
    
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            synchronized (B) {
    
    
                log.debug("lock B");
                try {
    
    
                    sleep(500);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (A) {
    
    
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");

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

// 测试结果如下
21:47:10.154 c.DeadLockTest [t1] - lock A
21:47:10.154 c.DeadLockTest [t2] - lock B
# 定位死锁
# 检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁

D:\juc\src\main\java\com\yyds\juc\monitor>jps
88304 Launcher
91744 DeadLockTest
92320 Jps
91716 KotlinCompileDaemon
41752 RemoteMavenServer36
43036

D:\juc\src\main\java\com\yyds\juc\monitor>jstack 91744
2023-03-08 21:50:08
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.102-b14 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002853800 nid=0x16640 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t2" #13 prio=5 os_prio=0 tid=0x000000001fcf5800 nid=0x149ec waiting for monitor entry [0x00000000202cf000]
   java.lang.Thread.State: BLOCKED (on object monitor)              # t2线程阻塞
        at com.yyds.juc.lock.DeadLockTest.lambda$main$1(DeadLockTest.java:38)
        - waiting to lock <0x000000076c8b0030> (a java.lang.Object) # 等待锁0x000000076c8b0030
        - locked <0x000000076c8b0040> (a java.lang.Object)          # t2锁住了0x000000076c8b0040
        at com.yyds.juc.lock.DeadLockTest$$Lambda$2/706277948.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

"t1" #12 prio=5 os_prio=0 tid=0x000000001fcf5000 nid=0x16174 waiting for monitor entry [0x00000000201cf000]
   java.lang.Thread.State: BLOCKED (on object monitor)               # t1线程阻塞
        at com.yyds.juc.lock.DeadLockTest.lambda$main$0(DeadLockTest.java:23)
        - waiting to lock <0x000000076c8b0040> (a java.lang.Object)  # 等待锁0x000000076c8b0040
        - locked <0x000000076c8b0030> (a java.lang.Object)           # t2锁住了0x000000076c8b0030
        at com.yyds.juc.lock.DeadLockTest$$Lambda$1/687241927.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

......


Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x000000001c4beae8 (object 0x000000076c8b0030, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x000000001c4c0f58 (object 0x000000076c8b0040, a java.lang.Object),
  which is held by "t2"

Java stack information for the threads listed above:
===================================================
"t2":
        at com.yyds.juc.lock.DeadLockTest.lambda$main$1(DeadLockTest.java:38)
        - waiting to lock <0x000000076c8b0030> (a java.lang.Object)
        - locked <0x000000076c8b0040> (a java.lang.Object)
        at com.yyds.juc.lock.DeadLockTest$$Lambda$2/706277948.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
"t1":
        at com.yyds.juc.lock.DeadLockTest.lambda$main$0(DeadLockTest.java:23)
        - waiting to lock <0x000000076c8b0040> (a java.lang.Object)
        - locked <0x000000076c8b0030> (a java.lang.Object)
        at com.yyds.juc.lock.DeadLockTest$$Lambda$1/687241927.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

insert image description here

9.2 Livelock

Livelock occurs when two threads change each other's end conditions, and finally no one can end.

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.LiveLockTest")
public class LiveLockTest {
    
    

    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
             // 期望减到 0 退出循环
            while (count > 0) {
    
    
                try {
    
    
                    sleep(200);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();

        
        new Thread(() -> {
    
    
            // 期望超过 20 退出循环
            while (count < 20) {
    
    
                try {
    
    
                    sleep(200);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

9.3 Hunger

Use sequential locking to solve the previous deadlock problem.

The following figure shows the deadlock problem

insert image description here

Solved by sequential locking

insert image description here

饥饿是一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束 。Sequential locking may cause starvation.

10、ReentrantLock

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

Compared with synchronized, it has the following characteristics

  • Can be interrupted

  • timeout can be set

  • Can be set as a fair lock

  • Support for multiple condition variables
    不过,与 synchronized 一样,都支持可重入

10.1 Reentrancy

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
If it is a non-reentrant lock, then when you get the lock for the second time, you will also be blocked by the lock

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.ReentrantLockTest")
public class ReentrantLockTest {
    
    
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
    
    
        method1();
    }
    public static void method1() {
    
    
        lock.lock();
        try {
    
    
            log.debug("execute method1");
            method2();
        } finally {
    
    
            lock.unlock();
        }
    }
    public static void method2() {
    
    
        lock.lock();
        try {
    
    
            log.debug("execute method2");
            method3();
        } finally {
    
    
            lock.unlock();
        }
    }
    public static void method3() {
    
    
        lock.lock();
        try {
    
    
            log.debug("execute method3");
        } finally {
    
    
            lock.unlock();
        }
    }
}
// 测试结果
22:25:22.766 c.ReentrantLockTest [main] - execute method1
22:25:22.767 c.ReentrantLockTest [main] - execute method2
22:25:22.767 c.ReentrantLockTest [main] - execute method3

10.2 Interruptible

package com.yyds.juc.lock;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.ReentrantLockInterrupt")
public class ReentrantLockInterrupt {
    
    

    public static void main(String[] args) {
    
    

        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {
    
    
            log.debug("启动...");
            try {
    
    
                // 注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
                log.debug("等锁的过程中被打断");
                return;
            }
        },"t1");

        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
    
    
            sleep(1000);
            t1.interrupt();
            log.debug("执行打断");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }
    }
}

// 测试结果如下
11:17:34.554 c.ReentrantLockInterrupt [main] - 获得了锁
11:17:34.554 c.ReentrantLockInterrupt [t1] - 启动...
11:17:35.555 c.ReentrantLockInterrupt [main] - 执行打断
11:17:35.555 c.ReentrantLockInterrupt [t1] - 等锁的过程中被打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.yyds.juc.lock.ReentrantLockInterrupt.lambda$main$0(ReentrantLockInterrupt.java:20)
	at java.lang.Thread.run(Thread.java:745)

10.3 Lock timeout

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.ReentrantLockOverTime")
public class ReentrantLockOverTime {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    

        ReentrantLock lock = new ReentrantLock();
        
        Thread t1 = new Thread(() -> {
    
    

            log.debug("启动...");
            try {
    
    
                if(!lock.tryLock(2L, TimeUnit.SECONDS)){
    
    
                    log.debug("获取锁超过2S,获取失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            try {
    
    
                log.debug("获得了锁");
            } finally {
    
    
                lock.unlock();
            }
        },"t1");

        // 主线程先获取锁
        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
    
    
          sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            lock.unlock();
        }

    }
}
// 测试结果如下
11:37:26.249 c.ReentrantLockOverTime [main] - 获得了锁
11:37:26.249 c.ReentrantLockOverTime [t1] - 启动...
11:37:28.263 c.ReentrantLockOverTime [t1] - 获取锁超过2S,获取失败,返回

10.3.1 The dining philosophers problem

There are five philosophers sitting around a round table.

  • They only do two things, think and eat, think for a while and eat, and then think after eating.

  • When eating, you need to use two chopsticks. There are 5 chopsticks on the table, and each philosopher has a chopstick on his left and right hands.

  • If the chopsticks are held by someone around you, you have to wait

筷子类

package com.yyds.juc.lock;

/**
 * 筷子类
 */
public class Chopstick {
    
    
    String name;

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

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

哲学家类

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;

/**
 * 哲学家类
 */
@Slf4j(topic = "c.Philosopher")
public class Philosopher extends Thread {
    
    
    Chopstick left;
    Chopstick right;

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

    private void eat() throws InterruptedException {
    
    
        log.debug("eating...");
        sleep(1000);
    }

    @Override
    public void run()  {
    
    
        while (true) {
    
    
            // 获得左手筷子
            synchronized (left) {
    
    
                // 获得右手筷子
                synchronized (right) {
    
    
                    // 吃饭
                    try {
    
    
                        eat();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
               // 放下右手筷子
            }
            // 放下左手筷子
        }
    }
}

测试类

package com.yyds.juc.lock;


public class PhilosopherTest {
    
    
    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();
    }
}
// 会发现执行后,出现死锁

10.3.2 Using tryLock to solve the dining philosophers problem

筷子类继承ReentrantLock

package com.yyds.juc.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 筷子类
 */
public class ChopstickV2 extends ReentrantLock {
    
    
    String name;

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

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

哲学家类利用tryLock

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;

/**
 * 哲学家类
 */
@Slf4j(topic = "c.PhilosopherV2")
public class PhilosopherV2 extends Thread {
    
    
    ChopstickV2 left;
    ChopstickV2 right;

    public PhilosopherV2(String name, ChopstickV2 left, ChopstickV2 right) {
    
    
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat() throws InterruptedException {
    
    
        log.debug("eating...");
        sleep(1000);
    }

    @Override
    public void run()  {
    
    
        while (true) {
    
    
            // 尝试获得左手筷子
            if(left.tryLock()){
    
    
                try {
    
    
                    // 尝试获取右手筷子
                    if(right.tryLock()){
    
    
                        try {
    
    
                            eat();
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        } finally {
    
    
                            right.unlock();
                        }
                    }
                } finally {
    
    
                    // 右手筷子没有获取,就释放左手筷子
                    left.unlock();
                }
            }
        }
    }
}

测试类

package com.yyds.juc.lock;


public class PhilosopherTestV2 {
    
    
    public static void main(String[] args) {
    
    
        ChopstickV2 c1 = new ChopstickV2("1");
        ChopstickV2 c2 = new ChopstickV2("2");
        ChopstickV2 c3 = new ChopstickV2("3");
        ChopstickV2 c4 = new ChopstickV2("4");
        ChopstickV2 c5 = new ChopstickV2("5");
        new PhilosopherV2("苏格拉底", c1, c2).start();
        new PhilosopherV2("柏拉图", c2, c3).start();
        new PhilosopherV2("亚里士多德", c3, c4).start();
        new PhilosopherV2("赫拉克利特", c4, c5).start();
        new PhilosopherV2("阿基米德", c5, c1).start();
    }
}

10.4 Condition variables

There are also condition variables in synchronized, which is the waitSet lounge. When the conditions are not met, enter waitSet to wait for
ReentrantLock. The condition variable is more powerful than synchronized in that it supports it 多个条件变量, which is like

  • synchronized is that those threads that do not meet the conditions are waiting in a lounge for messages

  • And ReentrantLock supports multiple lounges, there are lounges dedicated to waiting for smoke, lounges dedicated to waiting for breakfast, and wake up according to the lounge when waking up

使用要点:

  • A lock needs to be acquired before await

  • After await is executed, the lock will be released and enter conditionObject to wait

  • The await thread is woken up (or interrupted, or timed out) to re-compete for the lock lock

  • After the competition lock is successful, continue to execute after await

package com.yyds.juc.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

@Slf4j(topic = "c.ReentracntLockCondition")
public class ReentracntLockCondition {
    
    

    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitbreakfastQueue = lock.newCondition();
    static volatile boolean hasCigrette = false;
    static volatile boolean hasBreakfast = false;


    public static void main(String[] args) throws InterruptedException {
    
    

        new Thread(() -> {
    
    
            try {
    
    
                lock.lock();
                while (!hasCigrette) {
    
    
                    try {
    
    
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }

                log.debug("等到了烟");
            } finally {
    
    
                lock.unlock();
            }
        }, "烟").start();


        new Thread(() -> {
    
    
            try {
    
    
                lock.lock();

                while (!hasBreakfast) {
    
    
                    try {
    
    
                        waitbreakfastQueue.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }

                log.debug("等到了早餐");
            } finally {
    
    
                lock.unlock();
            }
        }, "早餐").start();


        sleep(1000);
        sendBreakfast();
        sleep(1000);
        sendCigarette();
    }

    private static void sendCigarette() {
    
    
        lock.lock();
        try {
    
    
            log.debug("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
    
    
            lock.unlock();
        }
    }

    private static void sendBreakfast() {
    
    
        lock.lock();
        try {
    
    
            log.debug("送早餐来了");
            hasBreakfast = true;
            waitbreakfastQueue.signal();
        } finally {
    
    
            lock.unlock();
        }
    }
}
// 测试结果如下
15:49:12.650 c.ReentracntLockCondition [main] - 送早餐来了
15:49:12.652 c.ReentracntLockCondition [早餐] - 等到了早餐
15:49:13.655 c.ReentracntLockCondition [main] - 送烟来了
15:49:13.655 c.ReentracntLockCondition [] - 等到了烟

10.5 Sequence control between threads

10.5.1 Fixed sequence control through wait/notify

先 2 后 1 打印

package com.yyds.juc.order;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.WaitNotifyOrder")
public class WaitNotifyOrder {
    
    

    static Object obj = new Object();
    // t2 运行标记, 代表 t2 是否执行过
    static boolean t2runed = false;

    public static void main(String[] args) {
    
    

        new Thread(() -> {
    
    
            synchronized (obj){
    
    
                // t2没有执行过,就等待
                while (!t2runed){
    
    
                    try {
    
    
                        obj.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
            log.debug("1");
        },"t1").start();


        new Thread(() -> {
    
    
            log.debug("2");
            synchronized (obj){
    
    
                // 修改运行标记
                t2runed = true;
                // 通知 obj 上等待的线程(可能有多个,因此需要用 notifyAll)
                obj.notifyAll();
            }
        },"t2").start();

    }
}
// 测试结果如下
17:40:08.113 c.WaitNotifyOrder [t2] - 2
17:40:08.115 c.WaitNotifyOrder [t1] - 1

10.5.2 Fixed sequence control by park/unpark

先 2 后 1 打印

package com.yyds.juc.order;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.ParkUnparkOrder")
public class ParkUnparkOrder {
    
    
    public static void main(String[] args) {
    
    

        /**
         * 没有『许可』时,当前线程暂停运行;
         * 有『许可』时,用掉这个『许可』,当前线程恢复运行
         */
        Thread t1 =  new Thread(() -> {
    
    
            // 暂停当前线程
            LockSupport.park();
            log.debug("t1");
        },"t1");


        /**
         * 给线程 t1 发放『许可』(多次连续调用 unpark 只会发放一个『许可』)
         */
        Thread t2 =   new Thread(() -> {
    
    
            log.debug("t2");
            LockSupport.unpark(t1);
        },"t1");


        t1.start();
        t2.start();
    }
}
// 测试结果如下
17:33:37.742 c.ParkUnparkOrder [t1] - t2
17:33:37.742 c.ParkUnparkOrder [t1] - t1

10.5.3 Alternate output through wait/notify

Thread 1 outputs a 5 times, Thread 2 outputs b 5 times, and Thread 3 outputs c 5 times. Now it is required to output abcabcabcabcabc how to achieve

package com.yyds.juc.order;


public class SyncWaitNotify {
    
    

    private int flag;
    private int loopNumber;

    public SyncWaitNotify(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();
                    }
                }
                System.out.print(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }
}
package com.yyds.juc.order;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.WaitNotifyOneByOne")
public class WaitNotifyOneByOne {
    
    



  public static void main(String[] args) {
    
    

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


  }
}
// 测试结果如下
abcabcabcabcabc

10.5.4 Alternate output via ReentrantLock

package com.yyds.juc.order;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantOneByOne {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    


        AwaitSignal as = new AwaitSignal(5);
        Condition aWaitSet = as.newCondition();
        Condition bWaitSet = as.newCondition();
        Condition cWaitSet = as.newCondition();

        new Thread(() -> {
    
    
            as.print("a", aWaitSet, bWaitSet);
        }).start();

        new Thread(() -> {
    
    
            as.print("b", bWaitSet, cWaitSet);
        }).start();

        new Thread(() -> {
    
    
            as.print("c", cWaitSet, aWaitSet);
        }).start();


        // 先唤醒a线程
        Thread.sleep(1000);
        as.lock();
        try {
    
    
            aWaitSet.signal();
        }finally {
    
    
            as.unlock();
        }
        
    }
}
// 测试结果如下
abcabcabcabcabc


class AwaitSignal extends ReentrantLock{
    
    

    // 循环次数
    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++) {
    
    
            this.lock();
            try {
    
    
                current.await();
                System.out.print(str);
                next.signal();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                this.unlock();
            }
        }
    }


}

10.5.5 Alternate output by park/unpark

package com.yyds.juc.order;

import java.util.concurrent.locks.LockSupport;

public class SyncPark {
    
    
    private int loopNumber;
    private Thread[] threads;

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

    public void setThreads(Thread... threads) {
    
    
        this.threads = threads;
    }

    public void print(String str) {
    
    
        for (int i = 0; i < loopNumber; i++) {
    
    
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(nextThread());
        }
    }

    private Thread nextThread() {
    
    
        Thread current = Thread.currentThread();
        int index = 0;
        for (int i = 0; i < threads.length; i++) {
    
    
            if(threads[i] == current) {
    
    
                index = i;
                break;
            }
        }
        if(index < threads.length - 1) {
    
    
            return threads[index+1];
        } else {
    
    
            return threads[0];
        }
    }

    public void start() {
    
    
        for (Thread thread : threads) {
    
    
            thread.start();
        }
        LockSupport.unpark(threads[0]);
    }

}




class TestUnpark{
    
    
    public static void main(String[] args) {
    
    
        SyncPark syncPark = new SyncPark(5);
        Thread t1 = new Thread(() -> {
    
    
            syncPark.print("a");
        });
        Thread t2 = new Thread(() -> {
    
    
            syncPark.print("b");
        });
        Thread t3 = new Thread(() -> {
    
    
            syncPark.print("c\n");
        });
        syncPark.setThreads(t1, t2, t3);
        syncPark.start();
    }
}


// 测试结果如下
abc
abc
abc
abc
abc

Supongo que te gusta

Origin blog.csdn.net/qq_44665283/article/details/129460018
Recomendado
Clasificación