第四章 共享模型之 管程 (下)

JUC并发编程系列文章

http://t.csdn.cn/UgzQi


文章目录

  • JUC并发编程系列文章
  • 前言
  • 七、wait -- notify
    • 1、为什么使用 wait
    • 2、 wait -- notify 原理
    • 3、API 介绍
  • 八、wait -- notify 的正确姿势
      • 示例一:sleep会拿着锁睡觉,阻碍其它线程执行
      • 示例二:wait -- notify 替代sleep
      • 示例三:wait -- notify 唤醒错了线程,产生虚假唤醒
      • 示例四:使用notifyAll唤醒所有线程
      • 示例五:while+wait 彻底解决虚假唤醒
      • 总结:wait -- notify 正确使用姿势
    • (同步)模式之保护性暂停
      • 实现:GuardedObject
      • 应用:GuardedObject
      • 扩展1:带超时版 GuardedObject
        • 测试没有超时
        • 测试超时
    • join原理:底层是wait,使用了保护性暂停模式
      • 扩展2:多任务版 GuardedObject ,一对一线程通信
    • (异步)模式之生产者/ 消费者
  • 九、park和Unpark
    • park和Unpark的原理
      • 先调用park再调用Unpark
      • 先调用Unpark 再调用park
  • 十、重新理解线程状态的转换
    • 情况一和情况二
    • 情况三和情况四
    • 情况五
    • 情况六
    • 情况七
    • 情况八
    • 情况九
    • 情况十
  • 十一、多把锁
    • 单把锁造成并发度低
    • 改进单把对象锁的问题
  • 十二、活跃性
    • 死锁
    • 定位死锁 P116
    • 哲学家就餐问题,导致死锁的经典案例
    • 活锁
    • 饥饿
      • 顺序加锁解决死锁问题 ,但容易产生饥饿问题
  • 十三、ReentrantLock (可重入的)
    • 可重入锁
    • 可打断 lock.lockInterruptibly()
    • (可设置)锁超时 lock.tryLock()🍓
      • lock.tryLock()无参,获取不到锁立刻返回,不会等待
      • lock.tryLock(TimeOut,TimeUnit) 有参,等待时间一到,没有获得锁立即返回
      • lock.tryLock()解决哲学家就餐问题💕
    • 公平锁
    • 条件变量:ReentrantLock的多个waitSet休息室
    • 同步模式之顺序控制🍕设计模式
      • 固定运行顺序
        • wait -- notify 版
        • ReentrantLock -- Condition 版
        • park -- Unpark 版
      • 交替输出
        • wait -- notify 版
        • ReentrantLock -- Condition 版
        • park -- Unpark 版
  • 本章总结


前言

不积硅步无以至千里,不积小流无以成江海。


七、wait – notify

1、为什么使用 wait

在这里插入图片描述
在这里插入图片描述

2、 wait – notify 原理

在这里插入图片描述

3、API 介绍

在这里插入图片描述

final static Object obj = new Object();

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

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





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





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

八、wait – notify 的正确姿势

在这里插入图片描述

示例一:sleep会拿着锁睡觉,阻碍其它线程执行

思考下面的解决方案好不好,为什么?

static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;


new Thread(() -> {
    synchronized (room) {
        log.debug("有烟没?[{}]", hasCigarette);
        if (!hasCigarette) {
            log.debug("没烟,先歇会!");
            sleep(2);
        }
        log.debug("有烟没?[{}]", hasCigarette);
        if (hasCigarette) {
            log.debug("可以开始干活了");
        }
    }
}, "小南").start();

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

sleep(1);
new Thread(() -> {
    // 这里能不能加 synchronized (room)? 不能
    hasCigarette = true;
    log.debug("烟到了噢!");
}, "送烟的").start();




//输出

20:49:49.883 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:49:49.887 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:49:50.882 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有烟没?[true] 
20:49:51.887 [小南] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以开始干活了
    

● 其它干活的线程,都要一直阻塞,效率太低
● 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
● 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没加 synchronized 就好像 main 线程是翻窗户进来的
● sleep妨碍其它人干活 解决方法,使用 wait - notify

示例二:wait – notify 替代sleep

● 解决了其它干活的线程阻塞的问题
● 但如果有其它线程也在等待条件呢?
但是如果有很多线程都在 wait 等待,notify 会不会叫错人,本来要唤醒线程 1 起来干活,结果把线程2 给叫醒了,但是线程 2的数据还没有准备好,这时又该怎么办?


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

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

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


输出

20:51:42.489 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:51:42.493 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以开始干活了
20:51:43.490 [送烟的] c.TestCorrectPosture - 烟到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有烟没?[true] 
20:51:43.490 [小南] c.TestCorrectPosture - 可以开始干活了

示例三:wait – notify 唤醒错了线程,产生虚假唤醒

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

new Thread(() -> {
    synchronized (room) {
        log.debug("有烟没?[{}]", hasCigarette);
        if (!hasCigarette) {
            log.debug("没烟,先歇会!");
            try {
                room.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("有烟没?[{}]", hasCigarette);
        if (hasCigarette) {
            log.debug("可以开始干活了");
        } else {
            log.debug("没干成活...");
        }
    }
}, "小南").start();

new Thread(() -> {
    synchronized (room) {
        Thread thread = Thread.currentThread();
        log.debug("外卖送到没?[{}]", hasTakeout);
        if (!hasTakeout) {
            log.debug("没外卖,先歇会!");
            try {
                room.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("外卖送到没?[{}]", hasTakeout);
        if (hasTakeout) {
            log.debug("可以开始干活了");
        } else {
            log.debug("没干成活...");
        }
    }
}, "小女").start();

sleep(1);
new Thread(() -> {
    synchronized (room) {
        hasTakeout = true;
        log.debug("外卖到了噢!");
        room.notify();
    }
}, "送外卖的").start();


输出  造成虚假唤醒
20:53:12.173 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:53:12.176 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:53:12.176 [小女] c.TestCorrectPosture - 外卖送到没?[false] 
20:53:12.176 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:53:13.174 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:53:13.174 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:53:13.174 [小南] c.TestCorrectPosture - 没干成活...

外卖送到了,小女没能干成活

示例四:使用notifyAll唤醒所有线程

就算唤醒了所有线程,这样可以保证能拿到结果的线程执行业务,但是那些拿不到结果的线程也被唤醒了,这时被唤醒完全没有屁用,如果没有拿到结果的线程很多,无疑是脱裤子放屁。那怎么解决这个问题示例五会给出答案

new Thread(() -> {
    synchronized (room) {
        hasTakeout = true;
        log.debug("外卖到了噢!");
        room.notifyAll();
    }
}, "送外卖的").start();


输出

20:55:23.978 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:55:23.982 [小南] c.TestCorrectPosture - 没烟,先歇会!
20:55:23.982 [小女] c.TestCorrectPosture - 外卖送到没?[false] 
20:55:23.982 [小女] c.TestCorrectPosture - 没外卖,先歇会!
20:55:24.979 [送外卖的] c.TestCorrectPosture - 外卖到了噢!
20:55:24.979 [小女] c.TestCorrectPosture - 外卖送到没?[true] 
20:55:24.980 [小女] c.TestCorrectPosture - 可以开始干活了
20:55:24.980 [小南] c.TestCorrectPosture - 有烟没?[false] 
20:55:24.980 [小南] c.TestCorrectPosture - 没干成活...

notifyAll唤醒了所有,但使用if+wait仅有一次机会,解决方法,一旦条件不成立,就没有重新判断的机会了.解决办法:while + wait,当条件不成立,再次 wait

示例五:while+wait 彻底解决虚假唤醒

//if  判断改成 while
if (!hasCigarette) {
    log.debug("没烟,先歇会!");
    try {
        room.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

线程被虚假唤醒后,会来到 room.wait() 方法,这时还是处于循环状态
继续执行代码,判断条件是否成立,成立的话再让当前线程继续调用 wai() 方法等待

while (!hasCigarette) {
    log.debug("没烟,先歇会!");
    try {
        room.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

总结:wait – notify 正确使用姿势

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

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

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

在这里插入图片描述

实现:GuardedObject

class GuardedObject {
    
    private Object response;
    private final Object lock = new Object();
    
    public Object get() {
        synchronized (lock) {
            // 条件不满足则等待
            while (response == null) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
            }
            return response; 
        }
    }
    
    public void complete(Object response) {
        synchronized (lock) {
            // 条件满足,通知等待线程
            this.response = response;
            lock.notifyAll();
        }
    }
    
}

应用:GuardedObject

一个线程等待另一个线程的执行结果

public static void main(String[] args) {
    GuardedObject guardedObject = new GuardedObject();
    
    new Thread(() -> {
        try {
            // 子线程执行下载
            List<String> response = download();
            log.debug("download complete...");
            guardedObject.complete(response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();
    
    log.debug("waiting...");
    
    // 主线程阻塞等待
    Object response = guardedObject.get();
    log.debug("get response: [{}] lines", ((List<String>) response).size());
}


执行结果
08:42:18.568 [main] c.TestGuardedObject - waiting...
08:42:23.312 [Thread-0] c.TestGuardedObject - download complete...
08:42:23.312 [main] c.TestGuardedObject - get response: [3] lines

扩展1:带超时版 GuardedObject

class GuardedObjectV2 {
    private Object response;
    private final Object lock = new Object();
    
    public Object get(long millis) {
        synchronized (lock) {
            // 1) 记录最初时间
            long begin = System.currentTimeMillis();
            // 2) 已经经历的时间
            long timePassed = 0;
            
            while (response == null) {
                // 4) 假设 millis 是 1000,结果在 400 时唤醒了,那么还有 600 要等
                long waitTime = millis - timePassed;
                log.debug("waitTime: {}", waitTime);
                
                if (waitTime <= 0) {
                    log.debug("break...");
                    break; 
                }
                
                try {
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                // 3) 如果提前被唤醒,这时已经经历的时间假设为 400
                timePassed = System.currentTimeMillis() - begin;
                
                log.debug("timePassed: {}, object is null {}", 
                          timePassed, response == null);
            }
            return response; 
        }
    }
    public void complete(Object response) {
        synchronized (lock) {
            // 条件满足,通知等待线程
            this.response = response;
            log.debug("notify...");
            lock.notifyAll();
        }
    }
}

测试没有超时

public static void main(String[] args) {
    GuardedObjectV2 v2 = new GuardedObjectV2();
    
    new Thread(() -> {
        sleep(1);
        v2.complete(null);
        sleep(1);
        v2.complete(Arrays.asList("a", "b", "c"));
    }).start();
    
    Object response = v2.get(2500);
    if (response != null) {
        log.debug("get response: [{}] lines", ((List<String>) response).size());
    } else {
        log.debug("can't get response");
    }
    
}


输出
08:49:39.917 [main] c.GuardedObjectV2 - waitTime: 2500
08:49:40.917 [Thread-0] c.GuardedObjectV2 - notify...
08:49:40.917 [main] c.GuardedObjectV2 - timePassed: 1003, object is null true
08:49:40.917 [main] c.GuardedObjectV2 - waitTime: 1497
08:49:41.918 [Thread-0] c.GuardedObjectV2 - notify...
08:49:41.918 [main] c.GuardedObjectV2 - timePassed: 2004, object is null false
08:49:41.918 [main] c.TestGuardedObjectV2 - get response: [3] lines

测试超时

// 等待时间不足
List<String> lines = v2.get(1500);

输出
08:47:54.963 [main] c.GuardedObjectV2 - waitTime: 1500
08:47:55.963 [Thread-0] c.GuardedObjectV2 - notify...
08:47:55.963 [main] c.GuardedObjectV2 - timePassed: 1002, object is null true
08:47:55.963 [main] c.GuardedObjectV2 - waitTime: 498
08:47:56.461 [main] c.GuardedObjectV2 - timePassed: 1500, object is null true
08:47:56.461 [main] c.GuardedObjectV2 - waitTime: 0
08:47:56.461 [main] c.GuardedObjectV2 - break...
08:47:56.461 [main] c.TestGuardedObjectV2 - can't get response
08:47:56.963 [Thread-0] c.GuardedObjectV2 - notify...

join原理:底层是wait,使用了保护性暂停模式

在这里插入图片描述

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

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

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

扩展2:多任务版 GuardedObject ,一对一线程通信

Futures 对象需要维护一个 GuardedObject 的集合,其中每个GuardedObject 都应该有一个唯一的 id

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.testThread09")
public class testThread09 {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }
        Thread.sleep(2000);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "内容" + id).start();
        }

        /** 输出
         * 19:57:39 [Thread-2] c.People - 开始收信 id:2
         * 19:57:39 [Thread-0] c.People - 开始收信 id:1
         * 19:57:39 [Thread-1] c.People - 开始收信 id:3
         * 19:57:41 [Thread-5] c.Postman - 送信 id:1, 内容:内容1
         * 19:57:41 [Thread-3] c.Postman - 送信 id:3, 内容:内容3
         * 19:57:41 [Thread-4] c.Postman - 送信 id:2, 内容:内容2
         * 19:57:41 [Thread-1] c.People - 收到信 id:3, 内容:内容3
         * 19:57:41 [Thread-2] c.People - 收到信 id:2, 内容:内容2
         * 19:57:41 [Thread-0] c.People - 收到信 id:1, 内容:内容1
         */
    }


}

//业务相关类
@Slf4j(topic = "c.Postman")
class Postman extends Thread {
    private int id;
    private String mail;
    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }
    @Override
    public void run() {
        GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
        log.debug("送信 id:{}, 内容:{}", id, mail);
        guardedObject.complete(mail);
    }
}

//业务相关类
@Slf4j(topic = "c.People")
class People extends Thread{
    @Override
    public void run() {
        // 收信
        GuardedObject guardedObject = Mailboxes.createGuardedObject();
        log.debug("开始收信 id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), 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();
    }
}

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

(异步)模式之生产者/ 消费者

在这里插入图片描述


import lombok.extern.slf4j.Slf4j;

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

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

        MessageQueue messageQueue = new MessageQueue(2);

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

         // 1 个消费者线程, 处理结果
        new Thread(() -> {
            while (true) {

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = messageQueue.take();
                log.debug("消费了"+message.toString());
            }
        }, "消费者").start();
    }
}


@Slf4j(topic = "c.Message")
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;
    }

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


@Slf4j(topic = "c.Message")
class MessageQueue {
    private LinkedList<Message> queue;
    private int capacity;

    public MessageQueue(int capacity) {
        this.capacity = capacity;
        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();
        }
    }
}



输出
20:57:09 [生产者3] c.testThread10 - try put message(33)
20:57:09 [生产者2] c.testThread10 - try put message(22)
20:57:09 [生产者0] c.testThread10 - try put message(00)
20:57:09 [生产者1] c.testThread10 - try put message(11)
20:57:09 [生产者1] c.Message - 库存已达上限, wait
20:57:09 [生产者3] c.Message - 库存已达上限, wait
20:57:10 [生产者1] c.Message - 库存已达上限, wait
20:57:10 [消费者] c.testThread10 - 消费了Message{id=0, message=0}
20:57:11 [消费者] c.testThread10 - 消费了Message{id=2, message=2}
20:57:12 [消费者] c.testThread10 - 消费了Message{id=3, message=3}
20:57:13 [消费者] c.testThread10 - 消费了Message{id=1, message=1}
20:57:14 [消费者] c.Message - 没货了, wait


九、park和Unpark

Unpark可以在 park之前调用,也可以在park 之后调用,都是用来恢复某个线程的运行,奇怪之处就是可以在线程暂停前调用 Unpark 就可以恢复线程的运行。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述

park和Unpark的原理

在这里插入图片描述

先调用park再调用Unpark

在这里插入图片描述
在这里插入图片描述

先调用Unpark 再调用park

在这里插入图片描述

十、重新理解线程状态的转换

在这里插入图片描述

情况一和情况二

在这里插入图片描述

public class TestWaitNotify {
    final static Object obj = new Object();
    
    public static void main(String[] args) {
        
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码...."); // 断点
            }
        },"t1").start();
        
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码...."); // 断点
            }
        },"t2").start();
        
        sleep(0.5);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
            obj.notifyAll(); // 唤醒obj上所有等待线程 断点
        }
        
    }
}

情况三和情况四

在这里插入图片描述

情况五

在这里插入图片描述

情况六

在这里插入图片描述

情况七

在这里插入图片描述

情况八

在这里插入图片描述

情况九

在这里插入图片描述

情况十

在这里插入图片描述

十一、多把锁

单把锁造成并发度低

在这里插入图片描述

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 输出:
 * 18:55:25 [小南] c.testThread11 - study 1 小时
 * 18:55:26 [小女] c.testThread11 - sleeping 2 小时
 *
 * 结论:
 *    如果一个类的两个方法互不相干,这时不能再使用对象锁,锁住整个对象了
 *    这时锁住整个对象,并发度会大大降低,也就造成了方法的串行,一个方法结束了才能调用另外一个方法。
 */
@Slf4j(topic = "c.testThread11")
public class testThread11 {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();

        new Thread(() -> {
            bigRoom.study();
        },"小南").start();

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


}

@Slf4j(topic = "c.testThread11")
class BigRoom {

    @SneakyThrows
    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 小时");
            Thread.sleep(2000);
        }
    }

    @SneakyThrows
    public void study() {
        synchronized (this) {
            log.debug("study 1 小时");
            Thread.sleep(1000);
        }
    }


}

改进单把对象锁的问题

在这里插入图片描述
在这里插入图片描述

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;


/**
 * 输出:
 * 19:01:38 [小南] c.testThread11 - study 1 小时
 * 19:01:38 [小女] c.testThread11 - sleeping 2 小时
 *
 * 结论:
 *    使用两个对象,让互补相干的方法各锁一个,就可以解决单把锁的并发度低的问题
 *    单必须保证两把锁锁住的方法没有任何数据的关联。
 */
@Slf4j(topic = "c.testThread11")
public class testThread11 {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();

        new Thread(() -> {
            bigRoom.study();
        },"小南").start();

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


}

@Slf4j(topic = "c.testThread11")
class BigRoom {
        private final Object studyRoom = new Object();
        private final Object bedRoom = new Object();

        @SneakyThrows
        public void sleep() {
            synchronized (bedRoom) {
                log.debug("sleeping 2 小时");
                Thread.sleep(2000);
            }
        }

        @SneakyThrows
        public void study() {
            synchronized (studyRoom) {
                log.debug("study 1 小时");
                Thread.sleep(1000);
            }
        }
}

十二、活跃性

死锁

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;

/**
 * 两个线程互相持有对方需要的锁就会陷入死锁。
 */
@Slf4j(topic = "c.tsetThread12")
public class tsetThread12 {
    public static void main(String[] args) {
        test();
        
    }

    public static void test(){
        Object A = new Object();
        Object B = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                try {
                    Thread.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 {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A) {
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");

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

定位死锁 P116

● 避免死锁要注意加锁顺序
● 另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

哲学家就餐问题,导致死锁的经典案例

在这里插入图片描述

import lombok.extern.slf4j.Slf4j;


/**  造成死锁
 * 19:45:50 [亚里士多德] c.Philosopher - eating...
 * 19:45:50 [苏格拉底] c.Philosopher - eating...
 * 19:45:51 [亚里士多德] c.Philosopher - eating...
 * 19:45:51 [苏格拉底] c.Philosopher - eating...
 * 19:45:52 [柏拉图] c.Philosopher - eating...
 * 19:45:53 [苏格拉底] c.Philosopher - eating...
 *
 */
@Slf4j(topic = "c.testThread13")
public class testThread13 {

    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 Chopstick {
    String name;

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

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


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

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

    private void eat() {
        log.debug("eating...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

}

使用 jconsole 检测死锁,发现

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

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

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,可以给一个睡眠的随机数,让两个线程错开执行,这样其中一个线程就无法决定另一个线程的结束条件,从而解决活锁问题.

在这里插入图片描述

饥饿

在这里插入图片描述

顺序加锁解决死锁问题 ,但容易产生饥饿问题

在这里插入图片描述
在这里插入图片描述

十三、ReentrantLock (可重入的)

在这里插入图片描述

可重入锁

在这里插入图片描述

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

static ReentrantLock lock = new ReentrantLock();

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

public static void method1() {
    lock.lock();
    try {
        log.debug("execute method1");
        method2();
    } finally {
        lock.unlock();
    }
}

public static void method2() {
    lock.lock();
    try {
        log.debug("execute method2");
        method3();
    } finally {
        lock.unlock();
    }
}

public static void method3() {
    lock.lock();
    try {
        log.debug("execute method3");
    } finally {
        lock.unlock();
    }
}


输出:证明是可重入的

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

可打断 lock.lockInterruptibly()

可打断是指当前线程等待锁的过程中,其他的线程可以通过 interrupt 方法终止当前线程的等待, synchronized还是ReentrantLock的lock方法也好,都是不可被打断的, 它们是别的线程持有着同步锁,就一直等待下去,反过来,当前线程再等待的时候希望可以终止这种等待可以使用 lock.lockInterruptibly() 方法,这样可以防止没有拿到锁的线程无限制的去等待下去,避免造成死锁的问题.

示例

ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {
    log.debug("启动...");
    
    try {
        //没有竞争就会获取锁
        //有竞争就进入阻塞队列等待,但可以被打断
        lock.lockInterruptibly();
        //lock.lock(); //不可打断
    } catch (InterruptedException e) {
        e.printStackTrace();
        log.debug("等锁的过程中被打断");
        return;
    }
    
    try {
        log.debug("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");

lock.lock();
log.debug("获得了锁");
t1.start();

try {
    sleep(1);
    log.debug("执行打断");
    t1.interrupt();
} finally {
    lock.unlock();
}

输出

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

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

ReentrantLock lock = new ReentrantLock();

Thread t1 = new Thread(() -> {
    log.debug("启动...");
    
    lock.lock();
    try {
        log.debug("获得了锁");
    } finally {
        lock.unlock();
    }
}, "t1");

lock.lock();
log.debug("获得了锁");
t1.start();

try {
    sleep(1);
    log.debug("执行打断");
    t1.interrupt();
    sleep(1);
} finally {
    log.debug("释放了锁");
    lock.unlock();
}


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

(可设置)锁超时 lock.tryLock()🍓

可打断是一种被动的死等,是其他线程调用等待线程的 lockInterruptibly() 来使等待的线程不再进行等待. 锁超时是以一种主动的方式实现避免死等的一种方式。

如果其他线程一直持有者锁没有释放,去尝试获得锁的线程不会一直死等,而是去等待一段时间,如果超过了等待的时间还没有获取到锁,也就是其他的线程还没有释放同步锁,就不会再进行等待了,表示这次获取锁失败,总之还是可以避免线程无限制的等待下去,避免死锁。

在这里插入图片描述

lock.tryLock()无参,获取不到锁立刻返回,不会等待

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j(topic = "c.ttestThread14")
public class testThread14 {
    @SneakyThrows
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

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

        lock.lock();
        log.debug("主线程获得了锁");
        t1.start();

        try {
            Thread.sleep(2000);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 21:20:58 [main] c.ttestThread14 - 主线程获得了锁
     * 21:20:58 [t1] c.ttestThread14 - 启动...
     * 21:20:58 [t1] c.ttestThread14 - t1获取立刻失败,返回
     */
}

lock.tryLock(TimeOut,TimeUnit) 有参,等待时间一到,没有获得锁立即返回

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.ttestThread14")
public class testThread14 {
    @SneakyThrows
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

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


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

        lock.lock();
        log.debug("主线程获得了锁");
        Thread.sleep(1000);
        t1.start();
        Thread.sleep(500);
        log.debug("主线程释放了锁");
        lock.unlock();


        Thread.sleep(500);
        log.debug("主线程又获得了锁");
        lock.lock();
        Thread.sleep(5000);
        t2.start();


    }

    /**
     * 21:33:36 [main] c.ttestThread14 - 主线程获得了锁
     * 21:33:37 [t1] c.ttestThread14 - t1启动...
     * 21:33:37 [main] c.ttestThread14 - 主线程释放了锁
     * 21:33:37 [t1] c.ttestThread14 - t1获得了锁
     * 21:33:38 [main] c.ttestThread14 - 主线程又获得了锁
     * 21:33:43 [t2] c.ttestThread14 - t2启动...
     * 21:33:45 [t2] c.ttestThread14 - t2等待2秒获取锁失败,返回
     */
}


lock.tryLock()解决哲学家就餐问题💕

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;


/**  造成死锁
 * 19:45:50 [亚里士多德] c.Philosopher - eating...
 * 19:45:50 [苏格拉底] c.Philosopher - eating...
 * 19:45:51 [亚里士多德] c.Philosopher - eating...
 * 19:45:51 [苏格拉底] c.Philosopher - eating...
 * 19:45:52 [柏拉图] c.Philosopher - eating...
 * 19:45:53 [苏格拉底] c.Philosopher - eating...
 *
 */
@Slf4j(topic = "c.testThread13")
public class testThread13 {

    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 Chopstick extends ReentrantLock{
    String name;

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

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


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

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

    private void eat() {
        log.debug("eating...");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {

        while (true) {
            // 获得左手筷子
            if(left.tryLock()){
                try {
                    // 获得右手筷子
                    if(right.tryLock()){
                        try {
                            // 吃饭
                            eat();
                        }finally {
                            // 放下右手筷子
                            right.unlock();
                        }
                    }
                }finally {
                    // 放下左手筷子
                    left.unlock();
                }
            }
        }

   }
}

公平锁

在这里插入图片描述

公平: 先来就能先执行
不公平: 不保证先来就先执行
公平锁一般没有必要,会降低并发度,后面分析原理时会讲解

条件变量:ReentrantLock的多个waitSet休息室

在这里插入图片描述

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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

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

        @SneakyThrows
        public static void main(String[] args) {

            new Thread(() -> {
                try {
                    lock.lock();
                    while (!hasCigrette) {
                        try {
                            waitCigaretteQueue.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    log.debug("等到了它的烟");
                } finally {
                    lock.unlock();
                }
            }).start();

            new Thread(() -> {
                try {
                    lock.lock();
                    while (!hasBreakfast) {
                        try {
                            waitbreakfastQueue.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    log.debug("等到了它的早餐");
                } finally {
                    lock.unlock();
                }
            }).start();

            Thread.sleep(1);
            sendBreakfast();
            Thread.sleep(1);
            sendCigarette();
        }

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

        private static void sendBreakfast() {
            lock.lock();
            try {
                log.debug("送早餐来了");
                hasBreakfast = true;
                waitbreakfastQueue.signal();
            } finally {
                lock.unlock();
            }
        }
    }


输出

22:22:08 [main] c.testThread15 - 送早餐来了
22:22:08 [Thread-1] c.testThread15 - 等到了它的早餐
22:22:08 [main] c.testThread15 - 送烟来了
22:22:08 [Thread-0] c.testThread15 - 等到了它的烟

同步模式之顺序控制🍕设计模式

固定运行顺序

wait – notify 版

在这里插入图片描述

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.testThread16")
public class testThread16 {
    static final Object lock = new Object();
    static boolean output = false;
    @SneakyThrows
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (lock){
                while (!output){
                    try {
                        //等待线程 2
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                log.debug("1");
            }

        },"T1");


        Thread t2 = new Thread(()->{
            synchronized (lock){
                log.debug("2");
                output = true;
                //唤醒线程 1
                lock.notify();
            }

        },"T2");

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

ReentrantLock – Condition 版


import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.testThread16")
public class testThread16 {
    static ReentrantLock lock = new ReentrantLock();

    static Condition condition = lock.newCondition();
    static boolean output = false;
    @SneakyThrows
    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(()->{
            try {
                //上锁
                lock.lock();
                while (!output){
                    try {
                        log.debug("t1进入等待");
                        condition.await();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
                log.debug("t1开始打印");
                log.debug("1");
            }finally {
                lock.unlock();
            }

        },"T1");


        Thread t2 = new Thread(()->{
            try {
                //上锁
                lock.lock();
                output = true;
                log.debug("2");
                condition.signal();
            }finally {
                //释放锁给 t1 使用
                lock.unlock();
            }
        },"T2");

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

输出
23:15:57 [T1] c.testThread16 - t1进入等待
23:15:57 [T2] c.testThread16 - 2
23:15:57 [T1] c.testThread16 - t1开始打印
23:15:57 [T1] c.testThread16 - 1


park – Unpark 版

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

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


       new Thread(()->{
            
            log.debug("2");
           LockSupport.unpark(t1);
        },"T2").start();

    }
}


输出
23:20:52 [T2] c.testThread17 - 2
23:20:52 [T1] c.testThread17 - 1

交替输出

wait – notify 版

在这里插入图片描述

代码示例

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.testThread17")
public class testThread17 {
    public static void main(String[] args) {
        /**输出
         * abcabcabcabcabcabcabcabcabcabc
         * 输出内容      等待标记   下一个标记
         * a            1        2
         * b            2        3
         * c            3        1
         * 三个变量没办法用布尔值表示
         */
        RePrint rePrint = new RePrint(1,10);

        new Thread(()->{
            rePrint.print("a",1,2);
        }).start();

        new Thread(()->{
            rePrint.print("b",2,3);
        }).start();

        new Thread(()->{
            rePrint.print("c",3,1);
        }).start();
    }

}

class RePrint{
    //初始标记
    private int initial;

    //循环打印次数
    private int num;

    public RePrint(int initial, int num) {
        this.initial = initial;
        this.num = num;
    }

    //打印方法
    public void print(String str,int sign , int next){
        //循环打印次数
        for (int i = 0;i < num;i++){
            //加同步锁
            synchronized (this){
                while (initial != sign){
                    try {
                        //初始标记和线程标记不一样就等待
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                initial = next;
                //唤醒所有线程进行下一轮判断
                notifyAll();
            }

        }
    }
}

ReentrantLock – Condition 版

示例

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.testThread17")
public class testThread17 {
    @SneakyThrows
    public static void main(String[] args) {
        /**输出
         * abcabcabcabcabcabcabcabcabcabc
         * 输出内容      当前休息室   下一个线程的休息室
         * a            a          b
         * b            b          c
         * c            c          a
         * 三个变量没办法用布尔值表示
         */

        RePrint rePrint = new RePrint(10);
        Condition a = rePrint.newCondition();
        Condition b = rePrint.newCondition();
        Condition c = rePrint.newCondition();

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

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

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


        Thread.sleep(1000);
        try {
            //让主线程上锁才能调用 signal 方法
            rePrint.lock();
            a.signal();
        }finally {
            rePrint.unlock();
        }
    }

}

class RePrint extends ReentrantLock{

    //循环打印次数
    private int num;

    public RePrint(int num) {
        this.num = num;
    }

    //打印方法
    @SneakyThrows
    public void print(String str, Condition sign , Condition next){
        //循环打印次数
        for (int i = 0;i < num;i++){
            //加同步锁
            lock();
            try {
                //让当前线程进入休息室
               sign.await();
               //输出
               System.out.print(str);
                //唤醒下一间休息室的线程
                next.signal();
            }finally {
                unlock();
            }
        }

        }

}

park – Unpark 版

示例

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "c.testThread17")
public class testThread17 {
    static Thread t1;
    static Thread t2;
    static Thread t3;

    @SneakyThrows
    public static void main(String[] args) {
        /**输出
         * abcabcabcabcabcabcabcabcabcabc
         */

        RePrint rePrint = new RePrint(10);

        t1 = new Thread(()->{
            rePrint.print("a",t2);
        });

        t2 = new Thread(()->{
            rePrint.print("b",t3);
        });

        t3 = new Thread(()->{
            rePrint.print("c",t1);
        });

        t1.start();
        t2.start();
        t3.start();
        //唤醒第一个线程开始循环
        LockSupport.unpark(t1);

    }

}

class RePrint{
    //打印方法
    @SneakyThrows
    public void print(String str, Thread next){
        //循环打印次数
        for (int i = 0;i < num;i++){
            //暂停当前线程
            LockSupport.park();
            System.out.print(str);
            //唤醒下一个线程
            LockSupport.unpark(next);
        }

    }

    //循环打印次数
    private int num;

    public RePrint(int num) {
        this.num = num;
    }



}


本章总结

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/4881.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Eyeshot 2023 重大变化--2023版本将被Crack

Eyeshot 2023是一个基于 Microsoft .NET Framework 的 CAD 控件。它允许开发人员快速将 CAD 功能添加到 WinForms 和 WPF 应用程序。Eyeshot 提供了从头开始构建几何体、使用有限元方法对其进行分析并在其上生成刀具路径的工具。还可以使用 CAD 交换文件格式导入或导出几何图形…

操作系统页表

虚拟内存 虚拟地址到物理地址的映射以实现隔离性 每个程序有独立的地址空间&#xff0c;不相互影响 页表 地址操作简单流程 CPU向虚拟地址va加载或写入数据–>CPU将va交给内存管理单元MMU–>SATP寄存器存放着内存中存放虚拟地址到物理地址的表单–>MMU通过SATP查找…

03_C++函数

函数的分文件编写1.1头文件-函数声明//swap.h文件 #include<iostream> using namespace std;void swap(int a, int b);1.2源文件-函数定义在源文件中&#xff0c;要加上头文件引用#include "swap.h"//swap.cpp文件 #include "swap.h"void swap(int a…

最少砝码(思维+找规律)

最少砝码 前n个可以表示0~k; 第n1个为x&#xff08;x>k&#xff09;; 最大可测maxnkx; 第n1个和前n个放两边 可测得x-k&#xff1b; 让x-kk1&#xff0c;得到x2k1&#xff1b; maxn3k1&#xff1b; k1 可以用(2k1) - (k-0) 表示 k2 可以用(2k1) - (k-1) 表示 … 2k1 可以用…

Compose 动画 (八) : Compose中的动画差值器 AnimationSpec

1. AnimationSpec是什么 Android Compose中的AnimationSpec用来自定义动画规格。 啥意思呢&#xff0c;其实就是类似于传统View体系中的差值器Interpolator&#xff0c;但是比起差值器&#xff0c;又提供了更多的功能。 根据其不同的子类&#xff0c;可以来控制动画执行时长、…

运维1.12了解集中式日志管理工具 ELK 的基本用法,包括数据采集、搜索和展示

ELK 是一个由三个开源项目组成的日志管理工具&#xff0c;它们分别是 Elasticsearch、Logstash 和 Kibana。Elasticsearch 是一个基于 Lucene 的搜索引擎&#xff0c;它可以提供实时的分布式搜索和分析能力&#xff1b;Logstash 是一个日志收集和处理工具&#xff0c;可以实现多…

《白帽子讲Web安全》世界观安全

1.Web安全简史1.1中国黑客简史对于现代计算机系统来说&#xff0c;在用户态的最高权限是root&#xff0c;也是黑客们最渴望能够获取的系统最高权限。不想拿到“root”的黑客&#xff0c;不是好黑客。在现实世界中&#xff0c;真正造成破坏的&#xff0c;往往并非那些挖掘并研究…

10秒去除WPS Office弹窗广告教程(2023.3.31最新)

目录 前言 步骤 1. 右击WPS Office&#xff0c;打开文件所在目录 2. 打开第二个文件夹 3. 进入office 6文件夹 4. 找到文件ksomisc.exe 5. 右击选择以管理员身份运行 6. 打开后是下面的界面&#xff0c;点击“高级(A)...” 7. 按照下图操作 8. 最后点击退出就设置完…

Mybatis配置之属性优化理解【transactionManager、dataSource、properties】

文章目录一.Mybatis配置之属性优化1.1 配置解析1.2 默认配置环境1.2.1 事务管理器&#xff08;transactionManager&#xff09;了解即可。1.2.2 数据源&#xff08;dataSource&#xff09;1.3 属性&#xff08;properties&#xff09;一.Mybatis配置之属性优化 1.1 配置解析 …

PyTorch 深度学习实战 |用 TensorFlow 训练神经网络

为了更好地理解神经网络如何解决现实世界中的问题&#xff0c;同时也为了熟悉 TensorFlow 的 API&#xff0c;本篇我们将会做一个有关如何训练神经网络的练习&#xff0c;并以此为例&#xff0c;训练一个类似的神经网络。我们即将看到的神经网络&#xff0c;是一个预训练好的用…

达梦回滚表空间的空间占用和释放

我们知道DML和DDL的区别之一是DML&#xff08;INSERT、UPDATE、DELETE&#xff09;操作数据库会产生重做日志和回滚日志&#xff0c;DDL不会产生重做日志和回滚日志&#xff0c;本章从系统表和动态视图上来分析达梦数据库DML和DDL对回滚表空间的占用&#xff0c;以及达梦回滚表…

C++11:可变参数模板/lambda表达式

1.可变参数模板 C11的新特性可变参数模板能够让我们创建可以接受可变参数的函数模板和类模板&#xff0c;相比C98和C03&#xff0c;类模板和函数模板中只能含固定数量的模板参数&#xff0c;可变参数模板无疑是一个巨大的改进。可是可变参数模板比较抽象&#xff0c;因此这里只…

vue路由守卫死循环及路由守卫使用

当前业务要求&#xff1a; 通过判断本地sessionstorge判断当前是否需要登陆页面 问题场景&#xff1a;用户登陆进入页面内部&#xff0c;点击退出登录&#xff0c;清除本地用户信息&#xff0c;页面跳转至登陆页面&#xff0c;使用浏览器的回退可以正常返回至项目内部。需要改…

kettle开发篇-更新-Day38

目录 前言&#xff1a; 一、更新组件介绍 1.1界面 1.2废话介绍 1.3重点解释 二、应用案例 2.1转换效果 2.2转换简介 三、总结 前言&#xff1a; 前面我们通过oracle的索引来处理单表超1亿的数据量表的查询问题&#xff0c;通过针对主键&#xff0c;展示的维度做多套索引…

如何使用码匠连接 GaussDB

目录 在码匠中集成 GaussDB 在码匠中使用 GaussDB 关于码匠 GaussDB 是华为推出的一个高性能、高可靠、高安全的分布式数据库管理系统。它采用多活架构&#xff0c;支持全球数据同步&#xff0c;可实现数据的实时同步和容灾备份&#xff0c;可满足不同业务场景下的数据管理…

《钢琴调律原理及应用》 笔记

【第一章 绪论】第一节 钢琴调律的概念 美国人威廉布雷德怀特于 1917 年发表了世界上第一部关于钢琴调律理论与技术的著作&#xff0c;书名为《钢琴调律与相关技术》 福岛琢郎于1950年发表一部名为《钢琴的构造调律修理》的专著 80年代初&#xff0c;在沈阳音院任教的张琨先生…

蓝桥杯正确的解题姿势

在做算法题的过程中最忌讳的就是上来就一顿乱敲&#xff0c;一开始我就是这样&#xff0c;但随着不断的刷题和老师的指导&#xff0c;总结了自己的刷题方法 示例题目 三角回文数 问题描述 对于正整数 n, 如果存在正整数 k使得 n123...kk(k1)/2 , 则 n 称为三角数。例如, 66066 …

弱监督实例分割 Box-supervised Instance Segmentation with Level Set Evolution 论文笔记

弱监督实例分割 Box-supervised Instance Segmentation with Level Set Evolution 论文笔记一、Abstract二、引言三、相关工作3.1 基于 Box 的实例分割3.2 基于层级的分割四、提出的方法4.1 图像分割中的层级模型4.2 基于 Box 的实例分割在 Bounding Box 内的层级进化输入的数据…

CentOS7+LAMP+DVWA靶机搭建

一、什么是DVWA Damn Vulnerable Web Application (DVWA)(译注&#xff1a;可以直译为&#xff1a;"该死的"不安全Web应用程序)&#xff0c;是一个编码差的、易受攻击的 PHP/MySQL Web应用程序。 它的主要目的是帮助信息安全专业人员在合法的环境中&#xff0c;练习…

【自动化】selenium配置步骤 | 备份本地资源

1、安装jdk 2、设置环境变量 .1、CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar .2、JAVA_HOME C:\Program Files\Java\jdk1.8.0_231 .3、PATH 新增 %JAVA_HOME%\bin 3、安装chrome 版本&#xff1a;85.0.4183.83 4、禁用chrom…