【记录】并发编程 - 学习日志(三)

4.6 wait / notify 

4.6.1 wait / notify 的原理

1. Thread-2 调用对象时,通过该对象访问其 Monitor 锁,查看 Owner 的值

2. 判断 Owner 是否为 null

若 Owner 不为 null,则进入 EntryList (阻塞状态 BLOCKED)

若 Owner 为 null,则将该线程存在 Owner 里 (拥有锁)

3. 判断 Owner 是否满足运行条件

若为是,则无需操作;

若为否,则调用 wait 方法进入 WaitSet(阻塞状态 WAITING)

线程进入 WaitSet 时会释放锁


BLOCKED 和 WAITING 两种阻塞状态的区别

BLOCKED 在 Owner 释放锁时被唤醒

WAITING 在调用 notify | notifyAll 方法时被唤醒

WaitSet 中的线程被唤醒后进入 EntryList

4.6.2 相关 API 

wait() :让拥有锁的线程进入 WaitSet

notify() :唤醒 WaitSet 中的某一个线程

notifyAll() :唤醒 WaitSet 中的全部线程

上述三种 API 都属于 Object 对象中的方法,需先获得此对象的锁才可被调用

wait()、wait(0) :让拥有锁的线程进入 WaitSet 进行无限制的等待,直到被 notify | notifyAll 唤醒

wait(long timeout) :让拥有锁的进程进入 WaitSet 等待 timeout 毫秒后被唤醒,也可直接被 notify | notifyAll 唤醒


 wait(long timeout) 和 sleep(long millis) 的相同点和不同点

1. 所属对象不同

        wait(long timeout) 属于 Object 对象中的方法

        sleep(long millis) 属于 Thread 对象中的方法

2. 调用约束不同

        sleep(long millis) 方法的调用没有限制

Thread.sleep(long millis);

        wait(long timeout) 方法的调用必须在 synchronized 代码块中

synchronized (对象) {

    对象.wait(long timeout);

}

3.  锁操作不同

        线程调用 sleep(long millis) 方法进入阻塞状态后不会释放锁

        线程调用 wait(long timeout) 方法进入阻塞状态后会释放锁

4. 阻塞状态相同

        线程调用 wait(long timeout) 和 sleep(long millis) 方法进入 TIMED_WAITING 状态

        线程调用 wait() 方法进入 WAITING 状态

4.6.3 wait / notify 的使用

step 1 
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

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

    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 {
                        Thread.sleep(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();

        }

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

10:08:55 [小南] c.Step1 - 有烟没?[false]
10:08:55 [小南] c.Step1 - 没烟,先歇会!
10:08:56 [送烟的] c.Step1 - 烟到了噢!
10:08:57 [小南] c.Step1 - 有烟没?[true]
10:08:57 [小南] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了

进程已结束,退出代码 0

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

不好,因为线程调用 sleep(long millis) 方法进入阻塞状态后并不会释放锁,所以在该线程被阻塞的时间里,其他线程也在被阻塞,影响运行效率。


step 2
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

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

    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("可以开始干活了");
                }
            }
        }, "小南").start();


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

        }

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

10:25:52 [小南] c.Step2 - 有烟没?[false]
10:25:52 [小南] c.Step2 - 没烟,先歇会!
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:53 [送烟的] c.Step2 - 烟到了噢!
10:25:53 [小南] c.Step2 - 有烟没?[true]
10:25:53 [小南] c.Step2 - 可以开始干活了

进程已结束,退出代码 0

思考上面的实现行吗,为什么?

不行,相较于 step 1 虽提高了运行效率,但又带来了新的问题【见 step 3】


step 3
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.step3")
public class step3 {
    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("没干成活...");
                }
            }
        }, "小南").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();
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notify();
            }
        }, "送外卖的").start();
    }
}
// 运行结果

10:33:49 [小南] c.step3 - 有烟没?[false]
10:33:49 [小南] c.step3 - 没烟,先歇会!
10:33:49 [小女] c.step3 - 外卖送到没?[false]
10:33:49 [小女] c.step3 - 没外卖,先歇会!
10:33:50 [送外卖的] c.step3 - 外卖到了噢!
10:33:50 [小南] c.step3 - 有烟没?[false]
10:33:50 [小南] c.step3 - 没干成活...

如果 WaitSet 中有多个线程,那么该如何正确唤醒我们想要唤醒的线程呢?

1. 唤醒 WaitSet 中的全部线程(调用 notifyAll 方法)【见 step 4】

2. 由线程自行判断是否满足运行条件( while + wait )【见 step 5】

在 WaitSet 中有多个线程的条件下,若调用 notify 方法唤醒了不是我们想要唤醒的线程,这种现象称为 虚假唤醒


step 4
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.step3")
public class step3 {
    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("没干成活...");
                }
            }
        }, "小南").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();
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();
            }
        }, "送外卖的").start();
    }
}
// 运行结果

10:46:46 [小南] c.step3 - 有烟没?[false]
10:46:46 [小南] c.step3 - 没烟,先歇会!
10:46:46 [小女] c.step3 - 外卖送到没?[false]
10:46:46 [小女] c.step3 - 没外卖,先歇会!
10:46:47 [送外卖的] c.step3 - 外卖到了噢!
10:46:47 [小女] c.step3 - 外卖送到没?[true]
10:46:47 [小女] c.step3 - 可以开始干活了
10:46:47 [小南] c.step3 - 有烟没?[false]
10:46:47 [小南] c.step3 - 没干成活...

进程已结束,退出代码 0

step 5 
package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.step3")
public class step3 {
    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);
                log.debug("可以开始干活了");
            }
        }, "小南").start();
        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                log.debug("可以开始干活了");

            }
        }, "小女").start();
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();
            }
        }, "送外卖的").start();
    }
}
// 运行结果

10:50:14 [小南] c.step3 - 有烟没?[false]
10:50:14 [小南] c.step3 - 没烟,先歇会!
10:50:14 [小女] c.step3 - 外卖送到没?[false]
10:50:14 [小女] c.step3 - 没外卖,先歇会!
10:50:15 [送外卖的] c.step3 - 外卖到了噢!
10:50:15 [小女] c.step3 - 外卖送到没?[true]
10:50:15 [小女] c.step3 - 可以开始干活了
10:50:15 [小南] c.step3 - 没烟,先歇会!

wait/notify 的使用模版

synchronized (对象) {

    while (!条件) {

        对象.wait();

    }

    ...
        
}

synchronized (对象) {

    对象.notifyAll();

}

4.7 保护性暂停模式

保护性暂停模式(Guarded Suspension),适用于一个线程等待另一个线程的执行结果的情况,让这两个线程关联同一个 GuardedObject 即可。

4.7.1 GuardedObject 

一个线程无限制地等待另一个线程的执行结果 

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test8")
public class Test8 {
    public static void main(String[] args) throws InterruptedException {
        GuardedObject1 go1 = new GuardedObject1();

        new Thread(() -> {
            try {
                Thread.sleep(1000);
                log.debug("t1 线程开始运行");
                go1.get("执行结果");
                log.debug("t1 线程继续运行其他指令");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

        log.debug("主线程开始运行");
        log.debug("执行结果:{}",go1.waiting());
    }
}

@Slf4j(topic = "c.GuardedObject1")
class GuardedObject1 {

    private Object response;

    public synchronized Object waiting() throws InterruptedException {
        while (response == null) {
            log.debug("等待 t1 线程的执行结果");
            this.wait();
        }
        return response;
    }

    public synchronized void get(Object response) {
        log.debug("t1 线程给出执行结果");
        this.response = response;
        log.debug("解除 WAITING 状态");
        this.notifyAll();
    }
}
// 运行结果

17:50:26 [main] c.Test8 - 主线程开始运行
17:50:26 [main] c.GuardedObject1 - 等待 t1 线程的执行结果
17:50:27 [t1] c.Test8 - t1 线程开始运行
17:50:27 [t1] c.GuardedObject1 - t1 线程给出执行结果
17:50:27 [t1] c.GuardedObject1 - 解除 WAITING 状态
17:50:27 [t1] c.Test8 - t1 线程继续运行其他指令
17:50:27 [main] c.Test8 - 执行结果:执行结果

进程已结束,退出代码 0

一个线程有限时地等待另一个线程的执行结果 

情况一:等待时长 < 获取执行结果所花费的时间

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) throws InterruptedException {
        GuardedObject2 go2 = new GuardedObject2();

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                log.debug("t1 线程开始运行");
                go2.get(null);
                Thread.sleep(2000);
                go2.get("执行结果");
                log.debug("t1 线程继续运行其他指令");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();

        log.debug("主线程开始运行");
        log.debug("执行结果:{}", go2.waiting(3000));
    }
}

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

    private Object response;

    public synchronized Object waiting(long timeout) throws InterruptedException {
        // duration 线程等待时长
        long duration = 0;
        while (response == null) {
            if (duration >= timeout) {
                break;
            }
            timeout -= duration;
            log.debug("等待 t1 线程的执行结果");
            // begin 线程开始等待时间
            long begin = System.currentTimeMillis();
            this.wait(timeout);
            // end 线程结束等待时间 (被唤醒或已等待 timeout 毫秒)
            long end = System.currentTimeMillis();
            duration = end - begin;
            log.debug("等待时长:{}", duration);
        }
        return response;
    }

    public synchronized void get(Object response) {
        log.debug("t1 线程给出执行结果");
        this.response = response;
        log.debug("解除 WAITING 状态");
        this.notifyAll();
    }
}
// 某次运行结果

10:13:27 [main] c.Test9 - 主线程开始运行
10:13:27 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:13:29 [t1] c.Test9 - t1 线程开始运行
10:13:29 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:13:29 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:13:29 [main] c.GuardedObject2 - 等待时长:1997
10:13:29 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:13:30 [main] c.GuardedObject2 - 等待时长:1012
10:13:30 [main] c.Test9 - 执行结果:null
10:13:31 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:13:31 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:13:31 [t1] c.Test9 - t1 线程继续运行其他指令

进程已结束,退出代码 0

情况二:等待时长 > 获取执行结果所花费的时间

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) throws InterruptedException {
        GuardedObject2 go2 = new GuardedObject2();

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                log.debug("t1 线程开始运行");
                go2.get(null);
                Thread.sleep(2000);
                go2.get("执行结果");
                log.debug("t1 线程继续运行其他指令");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();

        log.debug("主线程开始运行");
        log.debug("执行结果:{}", go2.waiting(5000));
    }
}

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

    private Object response;

    public synchronized Object waiting(long timeout) throws InterruptedException {
        // duration 线程等待时长
        long duration = 0;
        while (response == null) {
            if (duration >= timeout) {
                break;
            }
            timeout -= duration;
            log.debug("等待 t1 线程的执行结果");
            // begin 线程开始等待时间
            long begin = System.currentTimeMillis();
            this.wait(timeout);
            // end 线程结束等待时间 (被唤醒或已等待 timeout 毫秒)
            long end = System.currentTimeMillis();
            duration = end - begin;
            log.debug("等待时长:{}", duration);
        }
        return response;
    }

    public synchronized void get(Object response) {
        log.debug("t1 线程给出执行结果");
        this.response = response;
        log.debug("解除 WAITING 状态");
        this.notifyAll();
    }
}
// 某次运行结果

10:17:46 [main] c.Test9 - 主线程开始运行
10:17:46 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:17:48 [t1] c.Test9 - t1 线程开始运行
10:17:48 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:17:48 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:17:48 [main] c.GuardedObject2 - 等待时长:1991
10:17:48 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:17:50 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:17:50 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:17:50 [t1] c.Test9 - t1 线程继续运行其他指令
10:17:50 [main] c.GuardedObject2 - 等待时长:2004
10:17:50 [main] c.Test9 - 执行结果:执行结果

进程已结束,退出代码 0

duration 的值上下浮动为正常现象【问题不大】


N 个线程分别等待其他 N 个线程的返回结果 

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

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


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

@Slf4j(topic = "c.People")
class People extends Thread {
    @Override
    public void run() {
        try {
            GuardedObject3 go3 = Share.createGuardedObject3();
            Object response = go3.waiting(5000);
            log.debug("开始收信");
            log.debug("收信方id:{},信件内容:{}", go3.getId(), response);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

@Slf4j(topic = "c.Postman")
class Postman extends Thread {
    private int id;
    private Object response;

    public Postman(int id, Object response) {
        this.id = id;
        this.response = response;
    }

    @Override
    public void run() {
        GuardedObject3 go3 = Share.getGuardedObject3(id);
        log.debug("收信方id:{},信件内容:{}", id, response);
        log.debug("开始送信");
        go3.get(response);
    }

}

class Share {
    private static Map<Integer, GuardedObject3> map = new Hashtable<>();
    private static int id = 1;

    private static synchronized int obtainId() {
        return id++;
    }

    public static GuardedObject3 getGuardedObject3(int id) {
        return map.remove(id);
    }

    public static GuardedObject3 createGuardedObject3() {
        GuardedObject3 go3 = new GuardedObject3(obtainId());
        map.put(go3.getId(), go3);
        return go3;
    }

    public static Set<Integer> getIds() {
        return map.keySet();
    }
}

class GuardedObject3 {

    private int id;

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

    public int getId() {
        return id;
    }

    private Object response;

    public synchronized Object waiting(long timeout) throws InterruptedException {
        // duration 线程等待时长
        long duration = 0;
        while (response == null) {
            if (duration > timeout) {
                break;
            }
            timeout -= duration;
            // begin 线程开始等待时间
            long begin = System.currentTimeMillis();
            this.wait(timeout);
            // end 线程结束等待时间 (被唤醒或已等待 timeout 毫秒)
            long end = System.currentTimeMillis();
            duration = end - begin;
        }
        return response;
    }

    public synchronized void get(Object response) {
        this.response = response;
        this.notifyAll();
    }
}


// 某次运行结果

11:34:35 [Thread-3] c.Postman - 收信方id:3,信件内容:内容3
11:34:35 [Thread-5] c.Postman - 收信方id:1,信件内容:内容1
11:34:35 [Thread-4] c.Postman - 收信方id:2,信件内容:内容2
11:34:35 [Thread-5] c.Postman - 开始送信
11:34:35 [Thread-3] c.Postman - 开始送信
11:34:35 [Thread-4] c.Postman - 开始送信
11:34:35 [Thread-1] c.People - 开始收信
11:34:35 [Thread-0] c.People - 开始收信
11:34:35 [Thread-2] c.People - 开始收信
11:34:35 [Thread-1] c.People - 收信方id:2,信件内容:内容2
11:34:35 [Thread-0] c.People - 收信方id:1,信件内容:内容1
11:34:35 [Thread-2] c.People - 收信方id:3,信件内容:内容3

进程已结束,退出代码 0

4.8 生产者/消费者模式

保护性暂停模式中线程间为一对一关系,生产者/消费者模式中线程间为一对多关系(一个消费者对应多个生产者 )

消息队列遵从先进先出原则

消息队列有容量限制。队列为空时,消费者线程等待;队列已满时,生产者线程等待。

package com.rui.blocked;

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;

public class Test11 {
    public static void main(String[] args) {

        MessageQueues queues = new MessageQueues(2);

        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                try {
                    queues.put(new Message(id, "消费者" + id + "信息"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "生产者" + id).start();
        }

        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    queues.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者").start();
    }
}

@Slf4j(topic = "c.MessageQueues")
class MessageQueues {

    // 消息队列
    private LinkedList<Message> list = new LinkedList();

    // 消息队列容量
    private int capacity;

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

    public Message take() throws InterruptedException {
        synchronized (list) {
            // 消息队列为空时,消费者线程等待
            while (list.isEmpty()) {
                log.debug("消息队列为空,消费者线程等待");
                list.wait();
            }
            Message message = list.removeFirst();
            log.debug("消费者线程 take " + message.getId());
            list.notifyAll();
            return message;
        }
    }

    public void put(Message message) throws InterruptedException {
        synchronized (list) {
            // 消息队列已满时,生产者线程等待
            while (list.size() == capacity) {
                log.debug("消息队列已满,生产者线程等待");
                list.wait();
            }
            list.addLast(message);
            log.debug("生产者线程 put " + message.getId());
            list.notifyAll();
        }
    }
}

class Message {

    private int id;
    private Object value;

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

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}
// 某次运行结果

08:50:08 [生产者0] c.MessageQueues - 生产者线程 put 0
08:50:08 [生产者2] c.MessageQueues - 生产者线程 put 2
08:50:08 [生产者1] c.MessageQueues - 消息队列已满,生产者线程等待
08:50:09 [消费者] c.MessageQueues - 消费者线程 take 0
08:50:09 [生产者1] c.MessageQueues - 生产者线程 put 1
08:50:10 [消费者] c.MessageQueues - 消费者线程 take 2
08:50:11 [消费者] c.MessageQueues - 消费者线程 take 1
08:50:12 [消费者] c.MessageQueues - 消息队列为空,消费者线程等待

4.9 park / unpark 

原理

park / unpark 是 LockSupport 类中的方法

// 暂停当前线程的运行

LockSupport.park();

// 恢复当前线程的运行

LockSupport.unpark();
package java.util.concurrent.locks;

import sun.misc.Unsafe;

public class LockSupport {

    private static final sun.misc.Unsafe UNSAFE;
    
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

    public static void park() {
        UNSAFE.park(false, 0L);
    }

}

package sun.misc;

public final class Unsafe {
    
    public native void unpark(Object var1);

    public native void park(boolean var1, long var2);

}

图解

wait / notify / notifyAll 的调用顺序为 wait 在前,notify / notifyAll 在后

park / unpark 的调用顺序虽无先后之分,但会出现不同的情况


park 在前,unpark 在后

第一步,当前线程调用 LockSupport.park() 方法

第二步,查看 _counter 的值,此时该值为 0,获得 _mutex 互斥锁

第三步,线程进入 _cond 阻塞

第四步,将 _counter 赋值为 0

第五步,调用 LockSupport.unpark(Thread thread) 方法,将 _counter 赋值为 1

第六步,唤醒 _cond 中的 thread 线程  

第七步,thread 恢复运行

第八步,将 _counter 赋值为 0


unpark 在前,park 在后

第一步,调用 LockSupport.unpark(Thread thread) 方法,将 _counter 赋值为 1

第二步,thread 线程调用 LockSupport.park() 方法

第三步,查看 _counter 的值,此时该值为 1,thread 线程运行不受影响

第四步,将 _counter 赋值为 0


文字

每个线程都有一个专属 Parker 对象,该对象由 _counter,_cond 和 _mutex 三部分组成

若将线程看做是一位自驾游旅行者;将 Parker 对象看做是该旅行者驾驶的车辆;将 _cond 看做是该车辆行驶的状态;将 _counter 看做是该车辆的油量。

那么,线程调用 park 方法可以看做是车辆驶入了加油站。若车辆的油量为 0,则表示该车辆需要停车加油;若车辆的油量为 1,则表示车辆可继续行驶,无需停车加油。

park 方法可以看做是在通过查看车辆的油量判断车辆行驶的状态;unpark 方法可以看作是在给车辆加油使其满足行驶条件。


park 在前,unpark 在后

第一步,查看车辆的油量,此时该油量为 0

第二步,车辆油量不足,无法行驶,停车等待加油

第三步,加油

第四步,车辆继续行驶


unpark 在前,park 在后

第一步,加油

第二步,查看车辆的油量,此时该油量为 1

第三步,车辆继续行驶


不论是上述哪种情况,最终 _counter 都会被赋值为 0,不太恰当的理解是:车辆继续行驶的油耗

说些废话

本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值