java并发编程(3) 共享模型之管程 3


前言

这一系列资料基于黑马的视频:java并发编程,目前还没有看完,整体下来这是我看过的最好的并发编程的视频。下面是根据视频做的笔记。


1. wait和notify

1. 原理

主线程发现条件不足的时候可以调用wait方法,让线程进入WaitSet变成WAITING状态,进行阻塞,等到notify或者notifyAll的时候才会唤醒,这时候唤醒的线程进入EntryList中进行锁竞争,举个例子:

  • 有一个预言家,可以预言未来,所以有很多人都想要去请求占扑,但是一次只能帮一个人占扑
  • 这时候大家都在排队 (EntryList),当然抢票的方式可不是按排队的顺序决定的
  • 比如这时侯小明 (某个线程) 抢到了票,小明进入了房间内,其他人就只能等待了
  • 这时候小明惊奇的发现没带钱,尴尬的一幕发生了,没钱没诚意呀,占扑师不给语言了。但是总不能让小明自己一个人就占着房间不出来了吧,后面的人还要不要了,所以决定把小明先放到一个房子里面 (WaitSet) ,其他人 (其他线程) 继续抢名额
  • 等小明的朋友把钱送过来了,但是这时候已经有人在那个房间里面了,小明只能重新去抢名额了 (重新进入EntryList竞争)

在这里插入图片描述



2. Api介绍

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

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

比如下面这些代码:

@Slf4j
public class WaitNotifyTest {
    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("其它代码....");
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();

        // 主线程两秒后执行
        sleep.mySleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
//            obj.notify(); // 唤醒obj上一个线程,随机挑选一个waitSet的线程唤醒
            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

notify 的一种结果: 随机一个被唤醒
在这里插入图片描述
notifyAll的结果:全部被唤醒
在这里插入图片描述

  • wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止
  • wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify
//无限等待 
public final void wait() throws InterruptedException {
        this.wait(0L);
    }
	//等待一段时间
    public final native void wait(long var1) throws InterruptedException;

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0L) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        } else if (nanos >= 0 && nanos <= 999999) {
            if (nanos > 0) {
                ++timeoutMillis;
            }

            this.wait(timeoutMillis);
        } else {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    }



3. wait notify 的正确姿势

首先确认以下 sleep(long n) 和 wait(long n) 的区别:

  • sleep 是 Thread 方法,而 wait 是 Object 的方法
  • sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  • 它们状态 TIMED_WAITING
@Slf4j
public class Test4 {
    static final Object obj = new Dog();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            synchronized (obj){
                try {
                    obj.wait(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread");
        thread.start();
		
        sleep.mySleep(1);
        synchronized (obj){
            //wait释放了锁,syn能立刻获取锁
            log.debug("{}", Thread.currentThread());
        }
    }
}




例子sleep和wait对比:

设置一个例子,小南首先拿到了room的锁,但是没有烟没法干活,这时候其他5个线程也没法进入room干活,这时候派送烟的去送,到了烟后小南开始干活了,其他5个线程也开始干活了。

@Slf4j
public class Test5 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

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

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

        sleep.mySleep(1);
        new Thread(() -> {
            // 这里能不能加 synchronized (room)?
            //synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢!");
            //}
        }, "送烟的").start();
    }
    //DEBUG [小南] (21:50:31,746) (Test5.java:22) - 有烟没?[false]
    //DEBUG [小南] (21:50:31,748) (Test5.java:24) - 没烟,先歇会!
    //DEBUG [送烟的] (21:50:32,751) (Test5.java:47) - 烟到了噢!
    //DEBUG [小南] (21:50:33,761) (Test5.java:27) - 有烟没?[true]
    //DEBUG [小南] (21:50:33,761) (Test5.java:29) - 可以开始干活了
    //DEBUG [其它人] (21:50:33,761) (Test5.java:37) - 可以开始干活了
    //DEBUG [其它人] (21:50:33,764) (Test5.java:37) - 可以开始干活了
    //DEBUG [其它人] (21:50:33,765) (Test5.java:37) - 可以开始干活了
    //DEBUG [其它人] (21:50:33,765) (Test5.java:37) - 可以开始干活了
    //DEBUG [其它人] (21:50:33,765) (Test5.java:37) - 可以开始干活了
}

这样写可以实现要求,但是也有缺点:

  • 其他干活得线程都要一直阻塞,效率太低了
  • 小南线程必须2s之后才可以醒过来,尽管送烟得提前到了,小南也没法开始工作
  • 加了synchronized之后小南就好像反锁了门,烟根本没法送进来,main线程感觉是翻窗进来的
  • 解决方法:使用wait - notify




wait - notify解决: 随机唤醒
这时候问题就解决了,使用wait之后小南线程进入了WaitSet里面等,这时候其他线程就可以获取到room了,不会阻塞,等烟送来后小南进入EntryList,抢到room了,就可以开始干活了。

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

@Slf4j
public class Test5 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //如果被interrupt就会抛出异常,还可以往下执行
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

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

        sleep.mySleep(1);
        new Thread(() -> {
            // 这里能不能加 synchronized (room)?
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notify();
            }
        }, "送烟的").start();
    }
    //DEBUG [小南] (21:57:50,746) (Test5.java:22) - 有烟没?[false]
    //DEBUG [小南] (21:57:50,748) (Test5.java:24) - 没烟,先歇会!
    //DEBUG [其它人] (21:57:50,748) (Test5.java:42) - 可以开始干活了
    //DEBUG [其它人] (21:57:50,748) (Test5.java:42) - 可以开始干活了
    //DEBUG [其它人] (21:57:50,749) (Test5.java:42) - 可以开始干活了
    //DEBUG [其它人] (21:57:50,749) (Test5.java:42) - 可以开始干活了
    //DEBUG [其它人] (21:57:50,750) (Test5.java:42) - 可以开始干活了
    //DEBUG [送烟的] (21:57:51,749) (Test5.java:52) - 烟到了噢!
    //DEBUG [小南] (21:57:51,749) (Test5.java:32) - 有烟没?[true]
    //DEBUG [小南] (21:57:51,750) (Test5.java:34) - 可以开始干活了
}




wait - notifyAll示例: 全部唤醒

可以看到notifyAll唤醒了全部的线程,所以最后才会出现有外卖送到了,外卖的可以干活了而需要烟的没干成活。但是这时候由于Thread线程中是使用的if,if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了。解决方法:使用 while + wait,当条件不成立,再次 wait

@Slf4j
public class Test6 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    // 虚假唤醒
    public static void main(String[] args) {
        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.mySleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();//notify:随机唤醒,没用精确
            }
        }, "送外卖的").start();

        //DEBUG [小南] (22:03:28,956) (Test6.java:22) - 有烟没?[false]
        //DEBUG [小南] (22:03:28,958) (Test6.java:24) - 没烟,先歇会!
        //DEBUG [小女] (22:03:28,958) (Test6.java:43) - 外卖送到没?[false]
        //DEBUG [小女] (22:03:28,959) (Test6.java:45) - 没外卖,先歇会!
        //DEBUG [送外卖的] (22:03:29,968) (Test6.java:65) - 外卖到了噢!
        //DEBUG [小女] (22:03:29,968) (Test6.java:52) - 外卖送到没?[true]
        //DEBUG [小女] (22:03:29,970) (Test6.java:54) - 可以开始干活了
        //DEBUG [小南] (22:03:29,970) (Test6.java:31) - 有烟没?[false]
        //DEBUG [小南] (22:03:29,970) (Test6.java:35) - 没干成活...
        //
        //Process finished with exit code 0
    }
}




wait - notifyAll示例: while精确唤醒

在syn内部使用while 进行判断,就算中途被唤醒了也可以再次循环而不会说直接就没干成活…

@Slf4j
public class Test6 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    // 虚假唤醒
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                //使用while来每次都获取看看能不能拿到烟,防止虚假唤醒
                while (!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.mySleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();//notify:随机唤醒,没用精确
            }
        }, "送外卖的").start();

        //DEBUG [小南] (22:06:12,423) (Test6.java:22) - 有烟没?[false]
        //DEBUG [小南] (22:06:12,425) (Test6.java:25) - 没烟,先歇会!
        //DEBUG [小女] (22:06:12,425) (Test6.java:44) - 外卖送到没?[false]
        //DEBUG [小女] (22:06:12,425) (Test6.java:46) - 没外卖,先歇会!
        //DEBUG [送外卖的] (22:06:13,431) (Test6.java:66) - 外卖到了噢!
        //DEBUG [小女] (22:06:13,431) (Test6.java:53) - 外卖送到没?[true]
        //DEBUG [小女] (22:06:13,433) (Test6.java:55) - 可以开始干活了
        //DEBUG [小南] (22:06:13,433) (Test6.java:25) - 没烟,先歇会!
    }
}




总结套路

synchronized(obj){
	while(!条件判断){
        //条件不成立
        obj.wait();
	}
    //干他的活
}


//另一个线程使用notifyAll来唤醒,while 防止虚假唤醒的问题



2. 保护性暂停(线程一一对应)

在这里插入图片描述

下面的代码加上了超时退出功能:不让线程一直等待,下面模拟功能一个线程等待另一个线程的输入

@Slf4j
public class GuardedTest {
    //线程1等待线程2下载任务
    public static void main(String[] args) {
        /*GuardedObject guardedObject = new GuardedObject();
        new Thread(()->{
            log.debug("等待结果");
            //等待结果
            List<String> list = ( List<String>)guardedObject.get(2000);
            log.debug("结果有{}行", list.size());
        }, "t1").start();

        new Thread(()->{
            log.debug("执行下载");
            //执行下载
            try {
                List<String> download = DownLoader.download();
                guardedObject.complete(download);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }, "t2").start();*/

        GuardedObject guardedObject = new GuardedObject();
        new Thread(()->{
            log.debug("begin");
            Object response = guardedObject.get(2000);
            log.debug("结果是:{}", response);
        }, "t1").start();

        new Thread(()->{
            log.debug("begin");
            sleep.mySleep(3);
            guardedObject.complete(new Object());
        }, "t2").start();
        //模拟超时的情况
        //DEBUG [t1] (22:57:27,652) (GuardedTest.java:42) - begin
        //DEBUG [t2] (22:57:27,652) (GuardedTest.java:48) - begin
        //ERROR [t1] (22:57:29,665) (GuardedTest.java:71) - 超时了, 时间超过2秒
        //DEBUG [t1] (22:57:29,665) (GuardedTest.java:44) - 结果是:null
    }
}

@Slf4j
class GuardedObject{
    //结果
    private Object response;

    //获取结果
    public Object get(long timeout){
        synchronized (this){
            //开始时间
            long begin = System.currentTimeMillis();
            //经历的时间
            long passTime = 0L;
            while(response == null){
                //这一轮循环应该等待的时间
                long waitTime = timeout - passTime;
                if(passTime >= timeout){
                    log.error("超时了, 时间超过{}秒", timeout/1000);
                    break;
                }
                //添加超时退出功能
                try {
                    //没用结果就一直等待
                    //防止虚假唤醒的情况,比如有一个线程在没到timeout的时候唤醒了,
                    //但是这时候没到时间timeout,再次进入while循环,又会等timeout秒
                    //所以这里应该用timeout - passTime
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                passTime = System.currentTimeMillis() - begin;
            }
            return response;
        }
    }
    //产生结果
    public void complete(Object response){
        synchronized (this){
            //给结果变量赋值
            this.response =response;
            this.notifyAll();
        }
    }
}




在这里插入图片描述
下面是进行多个对象和线程之间的等待关系:三个线程等待收信,三个线程负责送信,中间使用一个媒介邮箱,快递员把信送入邮箱,用户负责收邮箱

流程大概是:用户先开始等待送信,并把信的编号在邮箱中保存,快递员看到了就可以开始送对应的编号。

public class test20 {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }
        //一秒之后开始送信
        sleep.mySleep(1);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "内容" + id);
        }
    }
}


//居民
@Slf4j
class People extends Thread{
    @Override
    public void run() {
        // 收信
        GuardedObject1 guardedObject = Mailboxes.createGuardedObject1();
        log.debug("开始收信 id:{}", guardedObject.getId());
        //获取信件的对象,线程等待5秒,等不到就算了
        Object mail = guardedObject.get(5000);
        log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
    }
}

//邮递员
@Slf4j
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() {
        GuardedObject1 guardedObject = Mailboxes.getGuardedObject1(id);
        log.debug("送信 id:{}, 内容:{}", id, mail);
        guardedObject.complete(mail);
    }
}

//Mailbox是快递员和用户的媒介
class Mailboxes {
    private static Map<Integer, GuardedObject1> boxes = new Hashtable<>();

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

    public static GuardedObject1 getGuardedObject1(int id) {
        return boxes.remove(id);
    }

    public static GuardedObject1 createGuardedObject1() {
        GuardedObject1 go = new GuardedObject1(generateId());
        boxes.put(go.getId(), go);
        return go;
    }
    //获取所有的id
    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}

// 增加超时效果
//住户和快递员的联系中介
class GuardedObject1 {

    // 标识 Guarded Object1
    private int id;

    public GuardedObject1(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;
                // 经历的时间超过了最大等待时间时,退出循环
                try {
                    if (timeout - passedTime <= 0) {
                        break;
                    }
                    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();
        }
    }
}



3.异步生产者消费者

在这里插入图片描述
一对多的关系,只要有生产者过来,那么消费者就可以进行消费,不需要一一对应。下面代码的流程:首先定义一个大小为2的消息队列,然后3个生产者往里面放消息,到第三个的时候发现队列满了,就等待,消费者开始消费消息,消费之后,生产者可以往里面继续放消息了。生产者只负责不断尝试消息,消费者负责不断消费消息,不是说尝生一个就消费一个的,要看线程的速度。

//java线程间通信的消息队列
@Slf4j
public class Test21 {
    public static void main(String[] args) {
        //最多只能有2个消息,所以到3的时候因为没有消费队列就一直等待
        MessageQueue queue = new MessageQueue(2);
        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(()->{
                queue.put(new Message(id, "值" + id));
            }, "生产者-" + i).start();
        }

        new Thread(()->{
            while(true){
                sleep.mySleep(1);
                Message message = queue.take();
            }
        }, "消费者").start();
    }
    //DEBUG [生产者-0] (22:19:49,804) (Test21.java:88) - 已存入消息:Message{id=0, value=值0}
    //DEBUG [生产者-1] (22:19:49,806) (Test21.java:88) - 已存入消息:Message{id=1, value=值1}
    //DEBUG [生产者-2] (22:19:49,806) (Test21.java:80) - 队列满了,生产者在等待
    //DEBUG [消费者] (22:19:50,815) (Test21.java:68) - 已消费了一个消息:Message{id=0, value=值0}
    //DEBUG [生产者-2] (22:19:50,817) (Test21.java:88) - 已存入消息:Message{id=2, value=值2}
    //DEBUG [消费者] (22:19:51,832) (Test21.java:68) - 已消费了一个消息:Message{id=1, value=值1}
    //DEBUG [消费者] (22:19:52,843) (Test21.java:68) - 已消费了一个消息:Message{id=2, value=值2}
    //DEBUG [消费者] (22:19:53,853) (Test21.java:60) - 队列为空,消费者在等待
}

@Slf4j
//java线程间通信的消息队列
final class MessageQueue {
    //存储消息的队列
    private LinkedList<Message> list = new LinkedList<>();
    //存储消息的容量
    private int capacity;

    //一开始初始化容量
    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    //获取消息
    public Message take() {
        //先检查队列是否为空,为空就等待
        synchronized (list) {
            while (list.isEmpty()) {
                //list是共享变量,可以作为锁
                try {
                    log.debug("队列为空,消费者在等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //有值了,就从队列头获取元素返回
            Message message = list.removeFirst();
            log.debug("已消费了一个消息:{}", message);
            list.notifyAll();
            return message;
        }
    }

    //存入消息
    public void put(Message message) {
        synchronized (list){
            //检查队列是否已满
            while(list.size() == capacity){
                try {
                    log.debug("队列满了,生产者在等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //新的队列消息加到尾部
            list.addLast(message);
            log.debug("已存入消息:{}", message);
            list.notifyAll();
        }

    }


}

class Message {
    private Integer id;
    private Object value;

    public Integer getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

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

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



4. unpark 和 park

1. 基本使用

它们是 LockSupport 类中的方法

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

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

下面是一些示例:

1、先 park 再 unpark

Thread t1 = new Thread(() -> {
	 log.debug("start...");
	 sleep(1);
	 log.debug("park...");
	 LockSupport.park();
	 log.debug("resume...");
},"t1");

t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);

输出结果:

18:43:50.765 c.TestParkUnpark [t1] - start... 
18:43:51.764 c.TestParkUnpark [main] - unpark... 
18:43:52.769 c.TestParkUnpark [t1] - park... 
18:43:52.769 c.TestParkUnpark [t1] - resume...



2、先 unpark再 park

Thread t1 = new Thread(() -> {
	 log.debug("start...");
	 sleep(2);
	 log.debug("park...");
	 LockSupport.park();
 	log.debug("resume...");
}, "t1");

t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1)

结果输出:可以看到就算是先unpark,线程再调用park的时候也会醒过来

18:43:50.765 c.TestParkUnpark [t1] - start... 
18:43:51.764 c.TestParkUnpark [main] - unpark... 
18:43:52.769 c.TestParkUnpark [t1] - park... 
18:43:52.769 c.TestParkUnpark [t1] - resume...





3、特点:

与 Object 的 wait & notify 相比

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



2. 原理

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

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


1. 先调用park再调用unpark

以下面这张图为例解释说明:

  • 当前线程调用 Unsafe.park() 方法
  • 检查 _counter ,本情况为 0(没有关联),这时,获得 _mutex 互斥锁进入等待队列阻塞
  • 线程进入 _cond 条件变量阻塞
  • 再次把_counter设置为0
    在这里插入图片描述

再调用Unpark:

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

在这里插入图片描述



2. 先调用unpark再调用park

以下面这张图为例解释说明:

  • 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  • 当前线程调用 Unsafe.park() 方法
  • 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
  • 设置 _counter 为 0
    在这里插入图片描述



3. 关于LockSupport的其他两个方法

//阻塞当前线程,最长不超过nanos纳秒,返回条件在park()的基础上增加了超时返回
void parkNanos(long nanos);
//阻塞当前线程,知道deadline时间(从1970年开始到deadline时间的毫秒数)
void parkUnitil(long deadline);



5. 重新理解线程状态转换

建议看视频:黑马状态转换

在这里插入图片描述

以一个线程 t 为例说明

1. NEW --> RUNNABLE

  • 当调用t.start() 方法时,由 NEW --> RUNNABLE



2. RUNNABLE <–> WAITING(建议看视频)

t线程用synchronized(obj)获取了对象锁后

  • 调用obj.wait()方法时,t 线程从RUNNABLE --> WAITING
  • 调用obj.notify(),obj.notifyAll(),t.interrupt()时
    1、竞争锁成功,t 线程从WAITING --> RUNNABLE
    2、竞争锁失败,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(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();

        // 主线程两秒后执行
        sleep.mySleep((long) 0.5);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
//            obj.notify(); // 唤醒obj上一个L线程
            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}



3. RUNNABLE <–> WAITING

  • 当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING
    1、注意是当前线程t 线程对象的监视器上等待

  • t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE



4. RUNNABLE<–> WAITING

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



5. RUNNABLE <–> TIMED_WAITING

t线程用synchronized(obj)获取了对象锁后

  • 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING
  • t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时
    1、竞争锁成功,t 线程 从TIMED_WAITING --> RUNNABLE
    2、竞争锁失败,t 线程 从TIMED_WAITING --> BLOCKED



6. RUNNABLE <–> TIMED_WAITING

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



7. RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING
  • 当前线程等待时间超过了 n 毫秒 ,当前线程从 TIMED_WAITING --> RUNNABLE



8. RUNNABLE <–> TIMED_WAITING

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



9. RUNNABLE <–> BLOCK

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



10. RUNNABLE <–> TERMINATED

当前线程所有的代码都运行完成的时候就会从RUNNABLE 到TERMINATED。






如有错误,欢迎指出!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值