JUC并发编程共享模型之管程(三)(下)

4.8 wait notify

根据前面很清楚了,关于为什么需要wait

  • 因为如果一个线程被调用,但是不符合运行条件,需要让他将锁释放,继续去等待,等待他的运行条件满足
  • 在不满足运行条件的情况下,我们不能给他重新放回EntryList中,让他继续去竞争,所以就有了WaitSet
  • 如果他运行条件满足了,再让他进入EntryList,重新去竞争锁

原理

在这里插入图片描述

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

API 介绍

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

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

@Slf4j
public class WaitNotifyTest01 {

    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(2);
        log.debug("唤醒obj上其他线程");
        synchronized (obj){
            obj.notify(); // 唤醒obj上一个线程
            //obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

notify的结果在这里插入图片描述

notifyAll的结果在这里插入图片描述

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

使用场景

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

  1. sleep 是Thread 方法,而wait 是Object的方法
  2. sleep 不需要强制和synchronized 配合使用,但 wait 需要和 synchronized 一起用
  3. sleep 在睡眠的同时,不会释放对象锁的, 但 wait 在等待时候会释放对象锁
  4. 它们状态 TIMED_WAITING
synchronized(lock) {
     while(条件不成立) {
       	lock.wait();
     }
     // 干活
}
//另一个线程
synchronized(lock) {
        lock.notifyAll();
}

同步模式之保护性暂停

1.定义

即 Guarded Suspension,用在一个线程等待另一个线程的执行结果

要点:

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

在这里插入图片描述

2.代码实现GuardedObject
@Slf4j
public class GuardObjectTest {

    public static void main(String[] args) {
        GuardObject guardObject = new GuardObject();

        new Thread(() -> {
            List<String> response = null;
            try {
                response = download();
                log.debug("下载结束");
                guardObject.compelete(response);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        log.debug("waiting。。。。");
        // 主线程阻塞等待
        Object response = guardObject.get();
        log.debug("获得 :[{}]",((List<String>) response).size());
    }

    private static List<String> download() throws InterruptedException {
        // 模拟下载
        List<String> list = new ArrayList<>();
        Thread.sleep(2000);
        for(int i = 0; i < 10; i++){
            list.add(i+"下载完毕");
        }
        return list;
    }

}


class GuardObject {

    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 compelete(Object response) {
        synchronized (lock){
            // 条件满足,通知等待线程
            this.response = response;
            lock.notifyAll();
        }
    }
}

在这里插入图片描述

3.带超时版本的GuardedObject
@Slf4j
public class GuardObjectTest02 {

    public static void main(String[] args) {
        GuardObject02 guardObject = new GuardObject02();

        new Thread(() -> {
            sleep(1);
            guardObject.compelete(null);
            sleep(1);
            guardObject.compelete(Arrays.asList("a","b","c"));
        }).start();

        // 不超时
        //Object response = guardObject.get(2500);

        // 超时
        Object response = guardObject.get(1500);

        if (response != null){
            log.debug("get response [{}]",((List<String>) response).size());
        }else{
            log.debug("no get response");
        }
    }

}

@Slf4j
class GuardObject02 {

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

    public Object get(long millis) {
        synchronized (lock){
            // 记录开始时间
            long begin = System.currentTimeMillis();
            // 已经过去的时间
            long timePass = 0;

            // 条件不满足则等待
            while (response == null){
                // 假设等待1000ms ,结果在400 ms时行了, 那么还有600要等
                long waitTime = millis - timePass;
                log.debug("需要等待的时间:{}", waitTime);

                if(waitTime <= 0){
                    log.debug("break...");
                    break;
                }
                try {
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 3) 如果提前被唤醒,这时已经经历的时间假设为 400
                timePass = System.currentTimeMillis() - begin;
                log.debug("timePass:{}, Object is null {}",timePass,response == null);
            }
            return response;
        }
    }

    public void compelete(Object response) {
        synchronized (lock){
            // 条件满足,通知等待线程
            this.response = response;
            lock.notifyAll();
        }
    }
}

在这里插入图片描述

异步模式之生产者消费者

1.定义

要点

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

在这里插入图片描述

2.实现
 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;
        }
    }

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

应用

        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();

在这里插入图片描述

4.9 park & unpark

基本使用

它们是 LockSupport 类中的方法

LockSupport.park()    // 暂停当前线程
LockSupport.unpark()     // 恢复某个线程的运行
先 park 再 unpark
@Slf4j
public class ParkTest01 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("start。。。");
            sleep(1);
            log.debug("park。。。");
            LockSupport.park();
            log.debug("resume....");
        },"t1");
        t1.start();
        
        sleep(3);
        log.debug("unpark");
        LockSupport.unpark(t1);
    }
}

在这里插入图片描述

先unpark 再 park
@Slf4j
public class ParkTest01 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("start。。。");
            sleep(1);
            log.debug("park。。。");
            LockSupport.park();
            log.debug("resume....");
        },"t1");
        t1.start();

        sleep(0.5);
        log.debug("unpark");
        LockSupport.unpark(t1);
    }
}

在这里插入图片描述

特点

与Object的 wait & notify 相比

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

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

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

4.10重新理解线程状态

在这里插入图片描述

假设有线程 t

  • NEW --> RUNNABLE
    • 调用 t.start()方法,由NEW --> RUNNABLE
  • RUNNABLE <–> WAITING
    • t 线程用 synchronized(obj) 获取了对象锁后
      • 调用obj.wait() 方法,RUNNABLE --> WAITING
      • 调用 obj.notify() ,obj.notifyAll(),t.interrupt() 时
        • 竞争锁成功,t 线程从 WAITING --> RUNNABLE
        • 竞争锁失败,t 线程从 WAITING --> BLOCKED
@Slf4j
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("其他代码"); // debug
            }
        },"t1").start();


        new Thread(() -> {
            synchronized (obj){
                log.debug("执行");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他代码");  // debug
            }
        },"t2").start();


        sleep(0.5);
        log.debug("唤醒obj上的其他线程");
        synchronized (obj){
            obj.notifyAll();  // 唤醒obj上所有等待线程  debug
        }
    }
}
  • RUNNABLE <–> WAITING

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

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

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

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

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

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

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

4.11 多把锁

多把不相干的锁

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

单个锁:

@Slf4j
class BigRoom {
    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 小时");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (this) {
            log.debug("study 1 小时");
            Sleeper.sleep(1);
        }
    }
}


@Slf4j
public class ManySynchronizedTest01 {
    public static void main(String[] args) {
        BigRoom room = new BigRoom();

        new Thread(() -> {
            room.sleep();
            Sleeper.sleep(2);
        }).start();

        new Thread(() -> {
            room.study();
            Sleeper.sleep(2);
        }).start();
    }
}

在这里插入图片描述

多个锁:

@Slf4j
class BigRoom {

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

    public void sleep() {
        synchronized (sleepRoom) {
            log.debug("sleeping 2 小时");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (studyRoom) {
            log.debug("study 1 小时");
            Sleeper.sleep(1);
        }
    }
}

@Slf4j
public class ManySynchronizedTest01 {
    public static void main(String[] args) {
        BigRoom room = new BigRoom();

        new Thread(() -> {
            room.sleep();
            Sleeper.sleep(2);
        }).start();

        new Thread(() -> {
            room.study();
            Sleeper.sleep(2);
        }).start();
    }
}

在这里插入图片描述

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

4.12 活跃性

死锁

  • 一个线程需要同时获取多把锁,这时就容易发生死锁
@Slf4j
public class LockDeadTest {

    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

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

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

在这里插入图片描述

**发生了死锁 **

  • jconsole 检测

在这里插入图片描述

在这里插入图片描述

  • jps 定位进程id,再用 jstack 定位死锁
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

  • 避免死锁要注意加锁顺序

  • 另外如果由于某个线程进入死循环,导致其他线程一直等待,对于这种情况linux可以通过top先定位到CPU占用高的Java进程,再利用 top -Hp 进程id 来定位是哪个线程,最后用jstack排查

活锁

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

类比死循环

饥饿

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

4.13 ReentrantLock

跟 synchronized 相比

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

与 synchronized 一样,都支持可重入

基本语法

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

可重入

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

@Slf4j
public class ReentrantLockTest01 {

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

在这里插入图片描述

可打断

@Slf4j
public class ReentrantLockTest02 {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("等锁的过程中被打断");
                return;
            }
            try{
                log.debug("获得了锁");
            }finally {
                lock.unlock();
            }
        },"t1");

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

    }
}

在这里插入图片描述

注意

  • 如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
@Slf4j
public class ReentrantLockTest03 {

    public static void main(String[] args) {
        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);
            t1.interrupt();
            log.debug("执行打断");
        } finally {
            log.debug("释放了锁");
            lock.unlock();
        }
    }
}

在这里插入图片描述

锁超时

立刻失败

@Slf4j
public class ReentrantLockTest03 {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            if (!lock.tryLock()){
                log.debug("没获取到锁,直接结束");
                return;
            }
            try {
                log.debug("获得了锁");
            }finally {
                lock.unlock();
            }
        },"t1");

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

    }
}

在这里插入图片描述

超时失败

@Slf4j
public class ReentrantLockTest05 {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        Thread t1 = new Thread(() -> {
            log.debug("启动...");
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)){
                    log.debug("尝试获取锁等1s,没获取到直接返回");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                log.debug("获得了锁");
            }finally {
                lock.unlock();
            }
        },"t1");

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

在这里插入图片描述

公平锁

ReentrantLock 默认是不公平的

@Slf4j
public class ReentrantLockTest06 {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(false);
        lock.lock();
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // 1s 之后去争抢锁
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " start...");
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
                lock.unlock();
            }
        }, "强行插入").start();
        lock.unlock();
    }
}

在这里插入图片描述

  • 强行插入,有机会在中间输出
  • 显示了不公平锁,有竞争

改为公平锁

ReentrantLock lock = new ReentrantLock(true);
  • 强行插入,总是在最后输出
  • 公平锁一般没有必要,会降低并发度

条件变量

  • synchronized 中也有条件变量,也就是WaitSet,当条件不满足时进入 waitSet 等待
  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的
    • synchronized 是不满足条件的线程都在WaitSet 等待
    • ReentrantLock 支持多个类似WaitSet的,对于WaitSet有更多的区分

使用要点

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待 await 的线程被唤醒(或打断、或超时)去重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
@Slf4j
public class ReentrantLockTest07 {
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitBreakfastQueue = lock.newCondition();
    static volatile boolean hasCigarette = false;
    static volatile boolean hasBreakfast = false;

    public static void main(String[] args) {
        new Thread(() -> {
            lock.lock();
            while(!hasCigarette){
                try {
                    waitCigaretteQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                log.debug("等到了他要的smoke");
            }finally {
                lock.unlock();
            }
        }).start();

        new Thread(() -> {
            lock.lock();
            while(!hasBreakfast){
                try {
                    waitBreakfastQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                log.debug("等到了他要的breakfast");
            }finally {
                lock.unlock();
            }
        }).start();

        sleep(1);
        sendBreakfast();
        sleep(1);
        sendCigarette();

    }

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

在这里插入图片描述

同步模式之顺序控制

1.固定运行顺序
  • 先打印 t2 再打印 t1
wait notify
@Slf4j
public class SequentialControllerTest01 {
    // 用来同步的对象
    static Object obj = new Object();
    // 判断t2是否运行过
    static boolean t2runed = false;

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            synchronized (obj){
                // 如果t2 没有执行过
                while(!t2runed){
                    // t1 等会
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("t1");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("t2");
            synchronized (obj){
                // 修改运行标记
                t2runed = true;
                // 通知obj上等待的线程 (可能多个)
                obj.notifyAll();
            }
        });

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

在这里插入图片描述

park unpark
@Slf4j
public class SequentialControllerTest02 {

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockSupport.park();
            System.out.println("t1");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("t2");
            LockSupport.unpark(t1);
        });
        t1.start();
        t2.start();
    }
}

在这里插入图片描述

park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』

2.交替输出

线程1输出 a 5次,线程 2 输出 b 5次, 线程 3 输出 c 5次。现在要求输出 abcabcabcabcabc

wait notify
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();
            }
        }
    }
}
@Slf4j
public class SequentialControllerTest03 {

    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();
    }
}
Lock 条件变量
@Slf4j
public class AwaitSignal extends ReentrantLock {

    // 循环次数
    private int loopNumber;

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

    public void start(Condition first){
        this.lock();

        try {
            log.debug("start");
            first.signal();
        }finally {
            this.unlock();
        }
    }

    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            this.lock();
            try {
                current.await();
                log.debug(str);
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                this.unlock();
            }
        }
    }

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

        as.start(aWaitSet);
    }
}

在这里插入图片描述

park unpark
@Slf4j
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.println(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]);
    }

    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");
        });
        syncPark.setThreads(t1,t2,t3);
        syncPark.start();

    }
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓风残月Lx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值