并发编程(四)

92 篇文章 0 订阅
10 篇文章 0 订阅

wait& notify

方法功能说明
void wait()释放对象锁, 并将当前线程加到 Monitor.WaitSet内(阻塞等待)
void notify()随机唤醒一个在 Monitor.WaitSet内(阻塞等待)的线程
void notifyAll()唤醒在 Monitor.WaitSet内(阻塞等待)的全部线程

*注: wait& notify是用于线程之间进行协作的, 必须持有锁才能调用, 且需要配合 synchronized同步块使用

在这里插入图片描述

  • sleep(long n)和 wait(long n)的区别:
  1. sleep是 Thread的方法, 而 wait是 Object的方法
  2. sleep可以不用和 synchronized配合使用, 但 wait需要和 synchronized一起用
  3. sleep是不会释放对象锁的, 而 wait会释放对象锁
    * 两个方法都会引起 "TIMED_WAITING"线程状态

wait& notify简单示例


public class App {
    private final static Object room = new Object();
    // 是否有烟?
    private static boolean hasCigarette = false;
    // 外卖是否到了?
    private static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            synchronized (room) {
                Thread t = Thread.currentThread();
                System.out.println(t.getName() + ": 有烟没? " + hasCigarette);
                while (!hasCigarette) {
                    System.out.println(t.getName() + ": 没烟, 先歇会!");
                    try {
                        // 进入阻塞等待, 直到有烟
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(t.getName() + ": 有烟没? " + hasCigarette);
                if (hasCigarette) {
                    System.out.println(t.getName() + ": 可以开始干活了.");
                } else {
                    System.out.println(t.getName() + ": 没干成活...");
                }
            }
        }, "小南").start();

        new Thread(() -> {
            synchronized (room) {
                Thread t = Thread.currentThread();
                System.out.println(t.getName() + ": 外卖送到没? " + hasTakeout);
                if (!hasTakeout) {
                     System.out.println(t.getName() + ": 没外卖, 先歇会!");
                    try {
                        // 进入阻塞等待, 直到外卖送到
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(t.getName() + ": 外卖送到没? " + hasTakeout);
                if (hasTakeout) {
                    System.out.println(t.getName() + ": 可以开始干活了.");
                } else {
                    System.out.println(t.getName() + ": 没干成活...");
                }
            }
        }, "小女").start();

        TimeUnit.SECONDS.sleep(1); // 主线程睡眠1秒后执行
        new Thread(() -> {
            synchronized (room) {
                Thread t = Thread.currentThread();
                hasTakeout = true;
                System.out.println(t.getName() + ": 你的外卖到了!");
                // 唤醒所有等待线程
                room.notifyAll();
            }
        }, "外卖员").start();
    }
}
小南: 有烟没? false
小南: 没烟, 先歇会!
小女: 外卖送到没? false
小女: 没外卖, 先歇会!
外卖员: 外卖到了噢!
小女: 外卖送到没? true
小女: 可以开始干活了.
小南: 没烟, 先歇会!

park& unpark

方法功能说明
void park()暂停当前线程
void unpark(Thread thread 将要恢复的线程)恢复指定线程的运行

park& unpark原理

  • 每个线程都拥有一个 Parker对象, 由三部分组成 _counter, _cond和 _mutex
  1. 线程比喻成一个旅人, Parker就像他随身携带的背包, cond就好比背包中的帐篷, _counter是背包中的备用干粮(0为耗尽, 1为充足)
  2. 调用 park, 检查是否需要停下来歇息
    (2.1) 如果备用干粮耗尽 _counter=0, 那么钻进帐篷歇息
    (2.2) 如果备用干粮充足 _counter=1, 那么继续前进
  3. 调用 unpark, 就好比令干粮充足
    (3.1) 如果该线程在帐篷, 就唤醒让他继续前进
    (3.2) 如果该线程正在运行中, 那么等下次再调用 park时, 可以消耗掉备用干粮, 而无需停留
    *注: 背包空间有限, 多次调用 unpark, 只会补充一份备用干粮 _counter=1

在这里插入图片描述

  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

wait& notify和 park& unpark的区别

  • wait& notify需要和 Monitor一起使用, 而 park& unpark不需要
  • park& unpark是以线程为单位的"阻塞& 唤醒", 而 notify只能随机或全部(notifyAll)唤醒等待线程
  • park& unpark可以先 unpark(预加 _counter=1), 而 wait& notify不能先 notify

同步模式之保护性暂停

  • JDK中 join和 Future的实现, 都是采用了这个模式
    * 虽然与 join方法模式上是相同的, 但功能上有区别的"保护性暂停"是一个线程等待另一个线程的结果, 而 join是一个线程等待另一个线程的结束
要点
一个线程同步等待另一个线程的执行结果
  • 保护性暂停示例 1

class GuardedObject {
    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) {
                long waitTime = millis - timePassed;
                System.out.println("waitTime: " + waitTime);
                if (waitTime <= 0) {
                    System.out.println("Time is out!");
                    break;
                }

                try {
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 3 唤醒后, 计算已经历的时间 (当前时间 - 开始时间) = 400
                timePassed = System.currentTimeMillis() - begin;
                System.out.println("timePassed: " + timePassed);
            }
            return response;
        }
    }
    // 条件满足, 通知等待线程(唤醒所有)
    public void complete(Object response) {
        synchronized (lock) {
            this.response = response;
            lock.notifyAll();
        }
    }
}

public class App {
    public static String download() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(1600);
        return "1234567890!";
    }

    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        new Thread(() -> {
            try {
                System.out.println("start download!");
                String response = download();
                System.out.println("complete download!");
                guardedObject.complete(response);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        System.out.println("waiting...");
        // 主线程阻塞等待
        Object response = guardedObject.get(2000);
        if (response != null) System.out.println("response is " + response);
    }
}
waiting...
start download!
waitTime: 2000
complete download!
timePassed: 1604
response is 1234567890!

  • 保护性暂停示例(多任务版) 2

class GuardedObject {
    private int id;
    public GuardedObject(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    // 返回信息
    private Object response;
    // 阻塞等待(附带超时时间)
    public Object get(long timeout) {
        synchronized (this) {
            // 1 起始时间
            long begin = System.currentTimeMillis();
            // 2 已经历的时间
            long timePassed = 0;
            while (response == null) {
                long waitTime = timeout - timePassed;
                if (waitTime <= 0) {
                    break;
                }
                try {
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 3 唤醒后, 计算已经历的时间 (当前时间 - 开始时间) = 400
                timePassed = System.currentTimeMillis() - begin;
            }
            return response;
        }
    }
    // 条件满足, 通知等待线程(唤醒所有)
    public void complete(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }
}
// 邮局(中间解耦类)
class PostOffice {
    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
    // 邮件编号
    private static int id = 1;
    private static synchronized int generateId() {
        return id++;
    }

    public static GuardedObject getGuardedObject(int id) {
        return boxes.remove(id);
    }
    // 生成邮件编号及对应的 GuardedObject
    public static GuardedObject createGuardedObject() {
        GuardedObject go = new GuardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}
// 邮件接受人
class Receiver extends Thread {
    @Override
    public void run() {
        // 每个接收人对应一个邮件编号及对应的 GuardedObject
        GuardedObject guardedObject = PostOffice.createGuardedObject();
        System.out.println("开始收信, 邮件编号: " + guardedObject.getId());
        // 接收人, 同步等待. 最多3秒
        Object mail = guardedObject.get(3000);
        System.out.println("收到信, 邮件编号: " + guardedObject.getId() + ", " + mail);
    }
}
// 派件员(生产者)
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 = PostOffice.getGuardedObject(id);
        System.out.println("开始派件, 邮件编号: " + id + ", " + mail);
        guardedObject.complete(mail);
    }
}

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

        TimeUnit.MILLISECONDS.sleep(1000);
        // 从邮局, 取所有可派送的邮件编号
        for (Integer id : PostOffice.getIds()) {
            new Postman(id, "内容").start();
        }
    }
}
开始收信, 邮件编号: 1
开始收信, 邮件编号: 2
开始派件, 邮件编号: 2, 内容
开始派件, 邮件编号: 1, 内容
收到信, 邮件编号: 1, 内容
收到信, 邮件编号: 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;
    }
}
// 队列
final 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()) {
                System.out.println("没有信息, 阻塞等待!");
                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) {
                System.out.println("信息数已到上限, 阻塞等待!");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(message);
            // 通知阻塞等待的线程, 唤醒所有线程
            queue.notifyAll();
        }
    }
}

public class App12 {
    public static String download() throws InterruptedException {
        TimeUnit.SECONDS.sleep(new Random().nextInt(3));
        return "got the data!";
    }

    public static void main(String[] args) {
        MessageQueue messageQueue = new MessageQueue(2);
        // 6个生产者线程, 下载任务
        for (int i = 0; i < 6; i++) {
            int id = i;
            new Thread(() -> {
                try {
                    System.out.println("id " + id + ", start download!");
                    String response = download();
                    System.out.println("id " + id + ", try put message!");
                    messageQueue.put(new Message(id, response));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "producer" + i).start();
        }
        // 1 个消费者线程, 处理结果
        new Thread(() -> {
            while (true) {
                Message message = messageQueue.take();
                String response = (String) message.getMessage();
                System.out.println("take message id " + message.getId() + ", content " + response);
            }
        }, "consumer").start();
    }
}
id 1, start download!
id 0, start download!
id 3, start download!
id 2, start download!
id 0, try put message!
id 4, start download!
id 5, start download!
没有信息, 阻塞等待!
id 5, try put message!
id 3, try put message!
信息数已到上限, 阻塞等待!
take message id 3, content got the data!
take message id 0, content got the data!
take message id 5, content got the data!
没有信息, 阻塞等待!
id 1, try put message!
id 2, try put message!
id 4, try put message!
take message id 1, content got the data!
take message id 2, content got the data!
take message id 4, content got the data!
没有信息, 阻塞等待!

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值