并发编程系列——wait原理的讨论(1)

Java并发编程—线程间协作方式wait()/notify()/notifyAll()原理分析 原文作者:tom有了cat 原文地址:java并发编程基础之等待通知机制wait/notify原理分析 一个线程修改了一个对象的值,而另一个线程感知了变化,然后进行了相应的操作,整个过程开始于一个线程,结束于另一个线程。前者是生产者,后者是消费者,这种模式在功能层面上实现了解耦。有下面一段代码,大家可以根据执行的结果和注释很清晰的明确这个wait和notify的工作机制,wait等待释放锁。代码如下,不再做具体描述: package juc.waitsleep; /** * @Descri. 阅读详情

失踪人口回归

好久没来写博客了,谢谢催更的网友你们的催更便是我写作的动力;但是这篇不想写spring了,写关于并发编程的,实用性比较大,特别对于面试;这篇博客来讨论关于synchronized关键字的wait方法的原理;笔者感触比较大,如果你想学好并发编程务必好好看看这篇文章,好了正篇开始。源于一个网友的问题如下

在这里插入图片描述

总结如下:synchronized关键字的调用wait方法进入等到的线程和因为拿不到锁而等待线程是否同一种状态?blocking?waiting?

别小看这个问题,要扯清这个问题需要大篇幅的文字,所以再次长文警告;而且笔者可以很负责任的告诉读者如果你能看懂这篇文章绝对会燃起你对并发编程学习的兴趣

关于blocking状态的线程

synchronized关键字的blocking

在多线程编程的情况下,假设我们定义了一把锁,如果现在有10个线程来获取这把锁那么肯定只有第一个线程可以获取到锁,从而进入临界区(所谓临界区就是被锁保护起来的代码块);其他获取不到锁的线程都会被阻塞(关于阻塞你就可以理解为CPU放弃调度这个线程了),但是这些被阻塞的线程JVM是怎么处理的呢?先看一张图
在这里插入图片描述
上图t1获取锁,如果在t1没有释放的情况下其他线程也来获取锁,结果肯定是获取不到,从而进入阻塞状态,但是这些被阻塞的线程如果不存某种关系将来唤醒的时候就很麻烦(比如先唤醒谁呢?有人肯定会说那肯定先唤醒最先阻塞的那个线程啊,关键是JVM如何知道哪个线程最先阻塞的呢?)为了解决这个麻烦JVM设计了一个EntryList的双向链表的队列来维护这些阻塞的线程;如上图这样 t2到tn被维护到了这个队列,当t1释放锁之后会去这个队列当中唤醒一个线程来获取锁,这里请读者们思考一些问题;到底是唤醒一个,还是全部唤醒呢?如果唤醒一个是随机唤醒还是顺序唤醒,如果是顺序唤醒是正序还是倒序呢?笔者直接给出答案,当t1释放锁的时候会从EntryList当中唤醒一个线程,而且顺序唤醒,而且倒序的,也就是先唤醒tn这个线程;但是值得注意的synchronized关键字是倒序唤醒,但是如果你使用ReentrantLock那么则是正序唤醒;那么这个结论如何证明了----笔者将会通过三个角度来证明
1、通过一个简单java应用来证明

2、是通过JDK内部关于ReentrantLock锁的实现来证明;(因为ReentrantLock和synchronized关键字都是实现同步锁,他们都有这么一个队列,原理差不多,其实就算我能通过ReentrantLock来证明这个双向列表的队列真实存在也不能说明synchronized关键字也有这么一个队列啊,确实是这样,但是由于ReentrantLock的这个队列是java语言实现的,比较容易看懂,毕竟是母语啊,所以先看懂java语言级别的实现——synchronized关键字是没有java级别源码可看的,他是通过C++代码来实现的,先看懂java的实现再来看C++会轻松点)

3、如果有可能我会通过JVM源码中来证明这个队列的存在

首先来看一个简单的java应用,代码如下(先仔细阅读以下代码,下文我会对代码做解释,博客里面所有代码放到文末链接,读者可以自己下载);

package com.shadow.test;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j(topic = "shadow")
public class TestSynchronized {
    static List<Thread> list = new ArrayList<>();
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {
                synchronized (lock) {
                    log.debug("thread executed");
                    try {
                        //这里的睡眠没有什么意义,仅仅为了控制台打印的时候有个间隔 视觉效果好
                        TimeUnit.MILLISECONDS.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "t" + i);//给每个线程去了一个名字 t1 t2 t3 ....

            list.add(t);
        }

        log.debug("---启动顺序 调度顺序或者说获取锁的顺序讲道理是正序0--9----");
     
        synchronized (lock) {
            for (Thread thread : list) {
                //这个打印主要是为了看到线程启动的顺序
                log.debug("{}-启动顺序--正序0-9", thread.getName());
                thread.start();//  CPU 调度?
                
                //这个睡眠相当重要,如果没有这个睡眠会有很大问题
                //这里是因为线程的start仅仅是告诉CPU线程可以调度了,但是会不会立马调度是不确定的
                //如果这里不睡眠 就有有这种情况出现
                // 主线程执行t1.start--Cpu没有调度t1--继续执行主线程t2-start cpu调度t2--然后再调度t1
                //虽然我们的启动顺序是正序的(t1--t2),但是调度顺序是错乱的  t2---t1
                
                TimeUnit.MILLISECONDS.sleep(1);
             }
             log.debug("-------执行顺序--正序9-0");
        }
    }
}

代码非常简单主线程main启动,然后实例化了10个线程对象t0-t9;继而把这个10个线程添加到一个List当中(注意这里仅仅是实例化了十(10)个线程,并没有启动,如果将来启动这10个线程他们的run方法里面的代码也非常简单,就是获取lock这把锁,然后打印一句话);添加到数组之后main线程接着往下执行;mian线程获取锁(这里一定能获取成功,因为那10个线程还没启动,锁处于自由状态,所以能被main获取);获取到锁之后main线程执行了一个for循环从list当中依次顺序获取到上面存入到list当中的那10个线程(由于ArrayList是有序的)故而取出的顺序肯定是有序的(t0-t9);取出来之后依次调用star方法启动这些线程;但是这里需要注意的是虽然我们已经保证取出来的线程是顺序的(t0-t9),而且我们也保证了这些线程的start方法是顺序调用的,但是你依然没法保证这些线程的调度(也就是我们常说的执行)顺序;为了保证t0-t9的调度顺序我这里在线程start之后,让main线程sleep了1毫秒;这样就能保证t0-t9线程的调度或者说执行顺序;至于为什么要保证他们的调度顺序?

  			 来解释一下为什么需要保证这个调度顺序呢?
             这里所有代码的意图就是顺序启动线程(顺序调度线程),这些线程启动之后会去拿锁(lock)
             肯定拿不到,因为这个时候锁被主线程持有
             主线程还在for循环没有释放锁,所以在for循环里面启动的线程都是拿不到锁的
             那么这些那不到锁的线程就会阻塞
             也就t0----t9阳塞之后他们被存到了一个队列当中
             这个JVM的源码中可以证明,我后面给大家看源码,
             总之你现在记住所有拿不到锁的线程会阻塞进入到Entrylist这个队列当中
             然后主线程执行完for循环后会释放放锁
             继而会去这个队列当中去唤醒一个个线程————随机唤醒还是顺序唤醒呢?
             假设是顺序唤醒,是倒序还是正序唤醒呢?
             需要证明这个问题,就要保证所有因为拿不到锁而进入到这个队列当中的线程
             他们的顺序必须是有序的,这样后面从他们的执行结果才能分析;
             假设你 进入到阻塞队列的时候都是随机的,那么后面唤醒线程执行的时候必然也是随机的
             那么则无法证明唤醒是否具备有序性
             为了保证进入到队列当中的线程调度是有序的,主线程睡眠很有必要  
             那么为什么主线程睡眠1下就能保证这些线程的顺序调度呢?这个问题读者可以思考一下后而我会重点分析
             好了现在我们来看结果

在这里插入图片描述
从上图可以看出首先10个线程的启动顺序(由于主线程睡眠了1毫秒故而启动顺序其实等于调度顺序)是t0-t9;因为在启动线程的时候主线程没有释放锁,所以t0-t9都因为拿不到锁进入了队列(EntryList),又因为t0-t9的调度(启动)顺序保证了,所以进入队列的顺序也保证了(t0先进入队列,t9最后进入队列);但是在主线程释放锁的时候,唤醒线程的顺序是都倒序的,先唤醒t9,最后唤醒t0;这里的结果可以说明JVM在从队列当中唤醒的时候是唤醒一个,而不是全部唤醒,因为如果是全部唤醒,那么这些线程的执行顺序肯定是乱的,只有唤醒一个,而且还是顺序唤醒才能保证执行顺序是具备规则的(t9-t0),而且是倒序唤醒的;那么这队列存在哪里呢?在java语言里使用synchronized关键字如果变成了一把重量锁(关于什么是重量锁下次分析),那么这个锁对象(本文当中的lock对象——Object lock = new Object())会关联一个C++对象——ObjectMonitor对象;这个监视器对象当中记录了持有当前锁的线程,记录了锁被重入的次数,同时他还有一个属性EntryList用来关联那些因为拿不到锁而被阻塞的线程;如下图所示(先不要关心WaitSet)

、在这里插入图片描述
好了到此为止你应该明白了我们在程序当中使用synchronized关键字的大概原理了吧,总结一下。当我们使用synchronized关键字来保护临界区的代码的时候,如果多线程并发情况下,持有锁的线程只有一个;其他竞争锁而不得线程会进入到当前锁对象关联的ObjectMonitor对象当中的双向链表的一个队列当中,并且是顺序进入的;但是当持有锁线程释放锁之后会从这个队列当中唤醒一个线程;从队列的末尾唤醒一个(先进后出);如果你没有看懂,请务必把文章翻上去在去看一遍或者从文章末尾把代码下载过去自己运行一遍,然后好好体会一下;因为只有把这里搞懂了下面笔者要写的内容才有意义,否则很难看懂下面的文章;

ReentrantLock的Blocking

这次代码改一下,不用synchronized关键来保护临界区,而是换成ReentrantLock,代码如下

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "s")
public class TestReentrantLock {
    
    static List<Thread> list = new ArrayList<>();
    //代码都没有变,只是把synchronized关键字变成了ReentrantLock
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {
                lock.lock();
                    log.debug("thread executed");
                    try {
                        TimeUnit.MILLISECONDS.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
               lock.unlock();
            }, "t" + i);
            list.add(t);
        }
        log.debug("---启动顺序 调度顺序或者说获取锁的顺序讲道理是正序0--9----");
        lock.lock();
            for (Thread thread : list) {
                log.debug("{}-启动顺序--正序0-9", thread.getName());
                thread.start();//  CPU 调度?
                TimeUnit.MILLISECONDS.sleep(1);
            }

            log.debug("-------执行顺序--正序9-0");

        lock.unlock();
    }
}

看看运行结果

在这里插入图片描述

从结果可以看到使用ReentrantLock来保护临界区的时候效果几乎和synchronized关键字相同,唯一不同的是当主线程释放锁之后去EntryList(EntryList其实是C++的队列,ReentrantLock其实不存在EntryList这个队列,但是他有一个对象FairSync或者NonfairSync这个对象维护了一个队列类似EntryList,文中为了方便都称之为EntryList吧)当中唤醒线程的时候是正序的(先进先出从);由于ReentrantLock是用java语言实现的,可以通过查阅JDK源码来看看他的原理;synchronized需要查询JVM的C++源码;如果大家对并发编程感兴趣可以给我留言,我会持续更新,看有么有机会来写一篇关于synchronized关键的底层C++代码实现;好了现在我们来翻翻JDK源码中对ReentrantLock的源码实现吧(其实,笔者早先写过一篇关于ReentrantLock的源码实现,而且你可以去b站search关键字"子路 AQS"有视频版的AQS源码分析),这里不做特别细致的源码分析,只做简单的源码分析;

首先看一下Node类设计,由于Node类是AbstractQueuedSynchronizer的一个内部类,我没有截取到类名,只截取到父类的名字(JDK源码中Node主要用来封装线程,因为node类里面有一个属性就是Thread类型的,你可以理解一个node对象就是一个线程)

在这里插入图片描述
主要关心三个属性

Node  prev    双向列表用来指向上一个node(也就是上一个线程)
Node  next    双向链表用来指向下一个node(也就是下一个线程)
Thread thread  当前node所封装的线程

如果单纯看这三个属性,可以理解Node类主要是为了线程之间有关联而设计的,因为如果没有这个Node那么单独一个Thread是很难描述清楚线程之间的关系的;比如上面代码中t0-t9他们的阻塞顺序靠一个Thread是很难表示的;有了这个Node就很好表示了,比如node0对象当中的thread=t0,prev=null;next=node1;而node1当中的thread=t1,prev=node0,next=node2…以此类推吧(实际当中Node类有很多属性的,这里不做讨论);

再来看AQS这个同步框架最核心的类的设计;同样我们只关心他的几个属性

在这里插入图片描述

Node head	//队列当中的对头,也就是第一个阻塞的线程封装出来的node对象
Node tail	//队列当中的对尾,也就是最后一个阻塞的线程封装出来的node对象
int state	//锁的重入次数

如果我们使用ReentrantLock来保护临界区当一个线程拿不到锁的时候,会把这个线程封装成为一个Node对象;比如当t7来获取锁的时候则持有锁的线程是main,重入次数为1,对头为t0,队尾为t6;自己被封装成为一个node对象(此时还没有进入队列,也就是还没有关联之前)如下图(介于图片大小问题中间的Node忽略了,比如t2所代表的node,t3所代表的node)

在这里插入图片描述
当封装好t7之后,这个时候t7所代表的node会进入到队列,进入队列之后如下图(介于图片大小问题中间的Node忽略了,比如t2所代表的node,t3所代表的node)

在这里插入图片描述
接下来通过idea当中的debug来说明一下上面的理论是否正确

在这里插入图片描述

上图的debug过程对于新手来说比较晦涩,可以多看几遍,或者文章末尾拿到笔者的代码自己去调试;主要需要说明的队列当中的对头关联并不是t0,而是一个thread=null,这个读者可以忽略(其实我在另一篇博客里面解释过了),也是就结果和我上面讲的有一些偏差,但是不影响,因为要解释这个thread=null代价比较大,读者可以把图换一下就一模一样了(实际情况如下图)
在这里插入图片描述
ReentrantLock当中的这个AQS双向链表队列相当于synchronized关键字当中的那个EntryList双向链表队列;只不过ReentrantLock这个队列是先进先出,而EntryList则相反是先进后出;这个上面已经通过例子证明了;

其实ReentrantLock的先进先出可以通过源码来说明的;我们可以看看他的解锁方法也就是unlock方法看他如何唤醒线程的就真相大白了;

在这里插入图片描述

好了说了这么多最后给大家总结一下

不管是synchronized还是使用ReentrantLock来做同步,并发情况下
所有拿不到锁的线程都会进入一个双向链表去阻塞
而进入这个队列当中阻塞的线程的状态就是blocking状态
至于什么是waiting状态呢?
同样我会通过synchronized和ReentrantLock两个技术点来说明

waitingg状态

首先我们假设这样一个场景jack是您们公司的一名程序员,他由于经常看笔者的博客;故而水平非常的高,经常能解决一些核心问题,所以逼格也很高;而各位读者就是程序员x,水平比较低,几乎没有逼格;假设你们老板在周末休息时间打电话叫所有程序员来加班,公司钥匙只有一把(进入到公司的人会把门锁了),所以能进到公司一定需要这把钥匙;这个时候jack来了,但是前面说过jack逼格很高他加班必须要你们老板给他安排一个女人,他才会啪啪啪(当然这里的啪啪啪是指敲键盘),不然他就会去休息,而你们是没有逼格的,进到公司就写代码;(有么有一点感同身受啊),基于这个场景我们来编程

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "s")
public class TestWait1 {

    static  Object object = new Object();//锁对象
    static boolean isWoman = false; // 是否有女人

    public static void main(String[] args) {
        new Thread(() -> {//jack
            synchronized (object){
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {
                        TimeUnit.SECONDS.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            }
        }, "jack").start();


        for (int i = 0; i <5 ; i++) {
            new Thread(() -> {
                synchronized (object){
                    log.debug("那些默默无闻的程序员coding");
                }
            }, "程序员"+i).start();
        }
    }

}

代码其实很简单,就是jack先获取到锁,然后发觉没有女人,不能啪啪啪(再次强调这里的啪啪啪指的是敲键盘);然后他就去休息了;然后其他彩笔程序员(for i=5)由于获取不到锁而不能工作无法启动;结果如下图

在这里插入图片描述

这样显然不合理,因为jack的女人问题,搞得其他五个人无法工作,可能被fire,这像极了我们平时,老板叫加班不敢不去,如果因为jack去不成那基本要被人事约谈,所以不合理;不合理的地方在于jack调用了sleep去阻塞,sleep阻塞的线程是无法释放锁的;假设有一种API能够让线程阻塞,同时又把锁释放了那就最好,jack他牛逼他去休息等女人,不影响其他人受虐心甘情愿的加班。JDK当中对于synchronized关键提供了一个wait方法可以实现上述功能

把代码修改一下,把sleep改成wait

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "s")
public class TestWait1 {

    static  Object object = new Object();//锁对象
    static boolean isWoman = false; // 是否有女人

    public static void main(String[] args) {
        new Thread(() -> {//jack
            synchronized (object){
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {
                        
                        //jack线程进入阻塞,但是释放了锁
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            }
        }, "jack").start();


        for (int i = 0; i <5 ; i++) {
            new Thread(() -> {
                synchronized (object){
                    log.debug("那些默默无闻的程序员coding");
                }
            }, "程序员"+i).start();
        }
    }

}

再次运行,其他五个人可以工作了,而jack则在等待女人(JVM没有退出,因为jack线程阻塞了)
在这里插入图片描述

但是五个彩笔只能写CRUD关键高并发的核心代码还是得jack来啊,如果他休息项目基本要黄;故而老板没有办法只能满足他——找个女人来,找个桥本有菜来给jack(这就是大神和你的区别吧可能);难么找来之后怎么唤醒jack呢?jdk当中提供了notify/notifyall来唤醒因为wait方法而被阻塞的线程

把代码再次改一下,添加一个boos线程,来满足jack的条件让isWoman=true,然后调用notifyAll来唤醒jack,叫醒之后jack啪啪啪完之后全部线程结束,JVM退出

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "s")
public class TestWait1 {

    static  Object object = new Object();//锁对象
    static boolean isWoman = false; // 是否有女人

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {

                        //jack线程进入阻塞,但是释放了锁
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            }
        }, "jack").start();


        for (int i = 0; i <5 ; i++) {
            new Thread(() -> {
                synchronized (object){
                    log.debug("那些默默无闻的程序员coding");
                }
            }, "程序员"+i).start();
        }



        //这里睡眠主要是为了视觉效果,没什么意义
        //5s之后叫醒jack
        TimeUnit.SECONDS.sleep(5);
        new Thread(() -> {//jack
            synchronized (object){
               isWoman=true;
               log.debug("jack 桥本有菜来了,你可以啪啪啪了");
               object.notifyAll();
            }
        }, "boss").start();

    }
}

运行结果如下

在这里插入图片描述

现在随着你们项目的推进代码越来越多,jack肾再好也啪不动了,故而你们老板再请了一个同样经常看笔者博客的大神——女程序员rose;rose由于经常给我的博客点赞水平比jack还牛逼,自然逼格更高,他如果加班则需要加班费,否则也是不会干活去休息。于是再改下代码(为了简单我把另外五个线程的代码注释了)

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "s")
public class TestWait1 {

    static  Object object = new Object();//锁对象
    static boolean isWoman = false; // 是否有女人
    static boolean isMoney = false; // 是否加钱了

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {

                        //jack线程进入阻塞,但是释放了锁
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            }
        }, "jack").start();




        new Thread(() -> {
            synchronized (object){
                while (!isMoney){
                    log.debug("没有加钱,先休息不干活");
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("干活了----有钱能使鬼推");
            }
        }, "rose").start();


//        for (int i = 0; i <5 ; i++) {
//            new Thread(() -> {
//                synchronized (object){
//                    log.debug("那些默默无闻的程序员coding");
//                }
//            }, "程序员"+i).start();
//        }



        //这里睡眠主要是为了视觉效果,没什么意义
        //5s之后叫醒jack
        TimeUnit.SECONDS.sleep(5);
        new Thread(() -> {//jack
            synchronized (object){
               isWoman=true;
               log.debug("jack 桥本有菜来了,你可以啪啪啪了");
               object.notifyAll();
            }
        }, "boss").start();

    }

}

上面代码你们老板并没有加钱只是找了女人,但是调用了notifyAll把所有因为wait方法而阻塞的线程全部叫醒了,但是只有jack会工作——因为满足了女人条件。但是没有满足加钱条件所以rose还是不会工作会一直阻塞——JVM不会退出;
在这里插入图片描述

当然老板如果把isMoney=true那么rose也会干活,并且干活完成之后JVM也会正常结束,这个笔者就不在演示了;
这里读者可以思考一个问题,因为老板调用的是notifyAll故而把所有线程叫醒了(jack和rose),但是如果老板调用的是notify(只会叫醒一个),那么老板叫醒的是谁呢?假设叫醒的是jack还好说,因为本身只满足了女人的条件叫醒他不为过,但是如果叫醒一个——刚好叫醒的是rose,而rose发现条件不满足继续wait,此时jack也在wait这样就不合理了,明明老板找来了桥本有菜,可是因为叫醒的人不对从而导致没有人工作,这下老板不是亏大了?(据我所知桥本有菜很贵的);那么notify的叫醒规则是什么呢?关于这个问题笔者下次更新吧;其实这也是synchronized关键字wait的一个缺陷;在ReentrantLock当中这个问题得到了完美解决;ReentrantLock当中也提供了类似wait的功能叫做Condition,和wait不同是Condition可以有多个休息室,比如因为女人而wait的jack可以进入A休息室,而因为加钱而wait的rose可以进入B休息室(当然你也可以让rose也进入A休息室);这样当条件满足的时候可以根据条件叫醒不同休息室的人;synchronized的wait方法是让线程进入一个队列WaitSet(休息室),不管你是因为女人还是因为加钱——jack和rose都进入WaitSet队列(同一个休息室,因为synchronized只有一个休息室);

在这里插入图片描述

因为拿不到锁而阻塞的线程(t0-t9)会进入EntryList阻塞;因为某个条件不满足被wait之后的线程进入WaitSet队列阻塞;那么为什么JVM不让wait的线程直接进入到EntryList当中呢?而是还要设计出来一个WaitSet的队列出来专门存放wait的线程呢?
其实主要是因为如果都存放在EntryList当中那么JVM是无法区分这些线程是为什么而被阻塞的;而且将来唤醒的时候也无法确定该不该唤醒;试想这么一个场景,t0持有锁,t1是因为拿不到锁而阻塞进入EntryList当中阻塞,t2早先持有锁,因为某个条件不满足调用了wait也进入到EntryList当中阻塞(这里我们假设JVM把所有阻塞的线程都放到EntryList当中);那么当t0释放锁之后讲道理他应该自动唤醒一个线程的,假设现在他唤醒的是t2就不合理啊,因为t2是wait而阻塞的不能自动唤醒,需要notify唤醒;所以JVM需要再设计一个waitSet来存放因为条件不满足调用了wait而阻塞的线程;

那么所有存在waitSet当中的线程状态就称之为waiting状态;现在如果你搞清楚了waiting和blocking那么接下来还有一个比较难的问题;所有waiting状态的线程必须经过blocking状态才能进入runing状态怎么理解?也就是一个waiting状态的线程是无法直接运行的,需要从waiting转换成blocking状态才能运行;说白了就是你调用notifyAll其实是把waitset当中所有阻塞线程转移到EntryList当中,然后当持有锁的线程释放锁之后再去EntryList当中的末尾(synchronized是末尾,ReentrantLock是最前面)唤醒一个执行;画幅图说明一下

现在持有锁的是main,假设t0-t9是blocking,jack和rose是waiting则如下图所示

在这里插入图片描述

如果现在主线程调用notifyAll方法会唤醒jack和rose,这里的唤醒其实是把它们转移到EntryList末尾,如下图所示
在这里插入图片描述
那么这个究竟是我吹牛逼还是真的如此呢?我通过一个例子和源码来证明
首先看一个简单的java例子

package com.shadow.test;
import lombok.extern.slf4j.Slf4j;
import org.omg.CORBA.TIMEOUT;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "s")
public class TestWait2 {

    static  Object object = new Object();//锁对象
    static boolean isWoman = false; // 是否有女人

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {
                        //jack线程进入阻塞,但是释放了锁
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            }
        }, "jack").start();


        TimeUnit.SECONDS.sleep(1);

        log.debug("1s之后主线程获取锁");
        log.debug("因为jack wait释放了锁,主线程能够获取到");
         log.debug("-----------关键看打印顺序----------------");
        synchronized (object) {
           
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    synchronized (object) {
                        log.debug("那些默默无闻的程序员coding");
                    }
                }, "t" + i).start();
                TimeUnit.MILLISECONDS.sleep(1);
            }

            isWoman=true;
            object.notifyAll();
        }


    }

}

解释一下代码,首先jack获取到了锁,然后调用了wait方法进入了waitSet阻塞,跟着主线程获取了锁,主线程获取锁之后启动了5个线程(t0-t4);这5个线程也要获取锁,但是因为现在锁被主线程持有所以这五个线程肯定是获取不到锁的,继而进入EntryList阻塞;启动完5个线程之后主线程满足条件isWoman=true;从而唤醒jack如果jack是即时唤醒执行的话,那么打印的顺序肯定是jack先执行(诚然结果也是jack先执行,但是其实这并不能说明notifyAll是即时唤醒),然后是t4-t0执行;但是笔者讲过notifyAll其实是转移线程到EntryList,也就是说当主线程调用完object.notifyAll();之后EntryList当中应该存在6个线程 t0、t1、t2、t3、t4、jack,如下图所示

在没有调用notifyAll之前

在这里插入图片描述

调用完notifyAll之后

在这里插入图片描述
主线程在调用完notifyAll之后就释放了锁,在这个时候JVM会去EntryList当中的末尾唤醒一个线程就是jack(这个上面我们已经证明过了——倒序);所以打印的结果应该是jack–>t4–>-t3—>t2---->t1—>t0;
在这里插入图片描述

但是这其实不能说明问题,因为诚如我上文说的notifyAll如果是即时唤醒也是这个打印顺序;但是如果改下代码就能看出问题了,把唤醒jack放到启动五个线程前面;

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;
import org.omg.CORBA.TIMEOUT;

import java.util.concurrent.TimeUnit;

@Slf4j(topic = "s")
public class TestWait2 {

    static  Object object = new Object();//锁对象
    static boolean isWoman = false; // 是否有女人

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {

                        //jack线程进入阻塞,但是释放了锁
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            }
        }, "jack").start();


        TimeUnit.SECONDS.sleep(1);

        log.debug("1s之后主线程获取锁");
        log.debug("因为jack wait释放了锁,主线程能够获取到");
        log.debug("-----------关键看打印顺序----------------");
        synchronized (object) {

           ------------------------------------- //注意顺序换了------------------------
            isWoman=true;
            object.notifyAll();


            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    synchronized (object) {
                        log.debug("那些默默无闻的程序员coding");
                    }
                }, "t" + i).start();
                TimeUnit.MILLISECONDS.sleep(1);
            }


        }


    }

}

那么情况就不同了如下图

在这里插入图片描述

那么执行顺序也就变了,jack的执行应该在最后(synchronized是倒序的);

在这里插入图片描述

这样应该足以证明问题了吧;假设你还不信,那么可以来翻阅JDK源码来证明这个问题,当然咯翻阅JDK源码主要是看ReentrantLock的实现,而不是wait的实现,wait的源码需要去看JVM源码C++代码;我们这里翻阅JDK源码只是来看看ReentrantLock当中的Condition是如何实现的,他几乎和wait的原理一样

把上面那个jack和rose加班的例子改一下,改成用ReentrantLock来实现(注意看代码)

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "s")
public class TestCondition1 {

    static ReentrantLock lock = new ReentrantLock();//锁对象
    static boolean isWoman = false; // 是否有女人
    static boolean isMoney = false; // 是否加钱了
    //因为钱不满足而进入阻塞的队列
    static Condition conditionMoney = lock.newCondition();
    //因为女人不满足而进入阻塞的队列
    static Condition conditionWoman = lock.newCondition();


    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            lock.lock();
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {

                        //jack线程进入阻塞,但是释放了锁
                        //进入特定的队列---conditionWoman
                       conditionWoman.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
           lock.unlock();
        }, "jack").start();

        new Thread(() -> {
           lock.lock();
                while (!isMoney){
                    log.debug("没有加钱,先休息不干活");
                    try {
                        // 进入特定的队列---conditionWoman
                        conditionMoney.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("干活了----有钱能使鬼推");
            lock.unlock();
        }, "rose").start();



        TimeUnit.SECONDS.sleep(5);
        new Thread(() -> {//jack
            lock.lock();
               isWoman=true;
               log.debug("jack 桥本有菜来了,你可以啪啪啪了");
               log.debug("因为是女人满足,从特定的队列当中(conditionWoman)唤醒");
               conditionWoman.signalAll();
            lock.unlock();
        }, "boss").start();

    }
}

这里定义了两个条件队列相当于定义了两个休息室,不同条件进入不同休息室,jack进入conditionWoman,而rose进入conditionMoney,老板最后只满足了桥本有菜故而只唤醒jack——conditionWoman.signalAll();rose是感觉不到的——甚至都不会醒来,刚刚synchronized关键字使用wait,调用notifyAll会把rose也叫醒,虽然rose最后还是去阻塞了(因为条件还是没有满足),但是终究是被叫醒了一次;使用ReentrantLock就没有这个问题了

在这里插入图片描述
自此可以知道ReentrantLock当中Condition可以实现和synchronized的wait一样的功能,而且她支持多条件,这比wait更加丰富(也是一个经典面试题——ReentrantLock和synchronze关键字的区别,什么情况下使用),你可以从这个方面去回答的,当然他们的区别不止这些,有机会在讨论吧

关键他的源码是怎样呢?也就是调用conditionWoman.await();是否如我所说会进入到一个waitSet的队列?
在这里插入图片描述

如果你有印象前面我们分析过所有因为拿不到锁的对象会进入到一个队列当中(ReentantLock当中的 FairSync这个对象所维护的双向链表队列),但是这里调用conditionWoman.await();则线程进入的是ConditionObject这个对象所维护的双向链表队列;这里足可证明waiting和blokcing两种不同状态的线程是维护在不同队列的(需要注意的是waitSet和EntryList是C++中的说法,由于ReentrantLock是java语言实现的,故而EntryList对应是ReentrantLock当中公平锁或者非公平锁中的队列,WaitSet对应的是ConditionObject这个双向链表队列);

再来看一下ReentrantLock的唤醒方法conditionWoman.signalAll();

在这里插入图片描述
结论:调用signalAll或者notifyAll方法都是把waiting状态的线程转化成为blocking状态——因为JDK内部是完成了一个队列的转移,而不是即时唤醒执行

当然最后的最后我们再次来看看ReentrantLock的Condition被唤醒之后的调用顺序是怎样的

package com.shadow.test;


import lombok.extern.slf4j.Slf4j;

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

@Slf4j(topic = "s")
public class TestConddition2 {

    static ReentrantLock lock = new ReentrantLock();//锁对象
    static boolean isWoman = false; // 是否有女人
    static Condition conditoon = lock.newCondition();
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            lock.lock();
                while (!isWoman){//判断是否有女人
                    log.debug("没有女人 我去等待老板安排 先休息,安排好之后叫醒我");
                    try {

                        //jack线程进入阻塞,但是释放了锁
                        conditoon.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("开始工作啪啪啪啪");
            lock.unlock();
        }, "jack").start();


        TimeUnit.SECONDS.sleep(1);

        log.debug("1s之后主线程获取锁");
        log.debug("因为jack wait释放了锁,主线程能够获取到");
        log.debug("-----------关键看打印顺序----------------");
        lock.lock();



            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                   lock.lock();
                        log.debug("那些默默无闻的程序员coding");
                   lock.unlock();
                }, "t" + i).start();
                TimeUnit.MILLISECONDS.sleep(1);
            }

        //注意顺序换了------------------------
        isWoman=true;
        conditoon.signalAll();



        lock.unlock();


    }
}

首先jack获取到了锁,然后调用了 conditoon.await();方法进入了waitSet(ConditionObject维护的双向链表)阻塞,然后主线程获取了锁,主线程获取锁之后启动了5个线程(t0-t4);这5个线程也要获取锁,但是因为现在锁被主线程持有所以这五个线程肯定是获取不到锁的,继而进入EntryList(非公平锁或者公平锁维护的双向链表队列)阻塞;启动完5个线程之后主线程满足条件isWoman=true;继而唤醒jack因为jack是在五个线程之后被唤醒的,也就是最后进入队列的 t0、t1、t2、t3、t4、jack,所以按照ReentrantLock先进先出的规则jack最后执行(和synchronized相反,前面我们已经证明过了synchronized是jack先执行)

在这里插入图片描述

一篇文章从晚上10点写到了早上八点写一个通宵,码字真的很难,所以更新的慢,哈哈,不知道能不能解答读者的一二困恼;还有我真心觉得并发编程要是你能把这两个队列搞懂学起来就不难了,而且会滋生很大的兴趣,笔者以前刚接触线程的时候非常的困扰,觉得线程好难,并发好难,锁好晦涩;但是记得一个不太明媚的早上我自己通过各种阅读和调试搞清楚了waitset和EntryList之后便觉得JDK的这些设计好精妙,从此对阅读源码有近乎疯狂的追求,所以希望你读完这篇文章之后能有所收获吧,过两天我会在b站边界线java上面更新一个视频文件主要就是讲这篇文章的知识点,最后提供文章中所有的代码读者可以自行调试;如果大家对并发编程感兴趣记得留言我尽快更新第二篇哈哈

代码地址(代码中用了lombok,自己装在idea)
链接:https://pan.baidu.com/s/1djfBWmqotbUWimZ1ixFvMQ
提取码:3v3u
复制这段内容后打开百度网盘手机App,操作更方便哦

【java】java wait 原理 synchronized ReentrantLock 唤醒顺序 学习一网友交流请问下,synchronized大并发的情况下,等待区的线程entrySet和waitSet里的线程分别对应Thread的哪个状态啊,都是wating吗?entryList是blocking状态吧,waitSet是waiting状态线程的生命周期,blocking和waiting不是同一种状态吗总结如下:synchronized关键字的调用wait方法进入等到的线程和因为拿不到锁而等待线程是否同一种状态?blocking?waiting? 阅读详情

相关推荐

Java并发编程中的线程通信:wait_notify机制详解

在多线程程序中,线程并非完全独立运行:它们可能需要共享数据、协调执行顺序(如A线程完成初始化后B线程才能开始工作),或共同维护某个状态(如生产者生产数据后消费者才能消费)。这种“线程间通信”的需求非常普遍,而机制是Java提供的最基础、最核心的线程通信工具。的底层原理(对象监视器、等待队列)正确使用的“黄金规则”(避免死锁、虚假唤醒)经典应用场景(生产者-消费者模型)常见误区与解决方案本文将按照“生活案例→核心概念→底层原理→代码实战→场景应用”的逻辑展开。

AI开发架构师 1362

wait原理的讨论

synchronized关键字的wait方法的原理;ReentrantLock的wait底层原理;waiting与blocking状态剖析;EntryList与WaitSet剖析

qq_41563912的博客 897

从逻辑门到存储阵列:NAND与NOR闪存命名与结构原理详解

在数字电路与半导体存储器领域,逻辑门是构建一切复杂功能的基础单元,其输入输出关系定义了计算与存储的基本原理。与非门(NAND)和或非门(NOR)作为通用逻辑门,其真值表和行为模式深刻影响了芯片设计。基于这些原理,现代闪存技术发展出了两种核心架构:NAND Flash和NOR Flash。这两种闪存的命名直接源于其内部存储单元阵列的连接方式,分别模拟了与非门和或非门的逻辑关系。这种结构差异决定了它们根本的技术价值:NOR Flash采用并联结构,支持快速随机读取,适用于代码存储与原地执行(XiP);而NAND

weixin_33377602的博客 337

桥本有菜java,深入浅出Extjs4.1.1(ExtJS组件、ExtJS对Ajax支持、ExtJS布局)配在线选课系统实战...

课程简介:ExtJS是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架,本课程是一套基于Extjs4.1.1版本进行全新讲解Extjs的课程。课程从基础开始,配合项目实战应用,让用户在最短的时间内掌握Extjs的高级应用。本课程是目前市面上Extjs最新版本的深入讲解Extjs基础入门系列:第一讲.ExtJs初识及其环境搭建第二讲.开始ExtJs梦想之旅第三讲.ExtJS工...

weixin_27224119的博客 1万+

知识见闻 - Salesforce公司发展史

不过,通过将自己开发的产品与公司收购的其它云产品进行结合,Salesforce可以为用户提供更多的产品种类,Salesforce可以继续强化自己的平台和产品套件属性,这一平台和套件的发展规模已经远远超出了一个单一的CRM的规模。他们围绕一个相对简单的产品向用户兜售云的概念,然后,他们开始开发其它产品,并寻找培育一个不断增长的生态系统的方法,最理想的情况是能驱动Salesforce的营收增长。现在,Salesforce通过打造更多的用例并改进自己产品的方式来直接应对竞争对手的竞争,从而捍卫自己现在的地位。

guoqx的专栏 1449

车辆贷款违约预测挑战赛

2021科大讯飞-车辆贷款违约预测挑战赛--方案简介车贷违约预测问题,目的是建立风险识别模型来预测可能违约的借款人。预测结果为借款人是否可能违约,属于二分类问题。偏数据挖掘的比赛,关键点是如何基于对数据的理解抽象归纳出有用的特征。站在大佬的视角,尝试学习总结,站在巨人的肩膀上,也许看得会更远一些。直接进入主题,开始学习套路,芜湖~特征工程1、常用库、数据导入import pandas as pd import numpy as np import lightgbm as lgb import xgboost

平平淡淡,戒急用忍,一生学闭嘴。 3341

java并发编程学习三——wait/notify与park/unpark

wait/notify与park/unpark

yx444535180的专栏 677

Windows 10 本地开发环境:用 ShardingSphere-Proxy 5.4.1 快速搭建 MySQL 分库分表测试环境

本文详细介绍了在Windows 10系统中使用ShardingSphere-Proxy 5.4.1快速搭建MySQL分库分表测试环境的完整教程。从环境准备、MySQL配置到ShardingSphere-Proxy的核心设置,逐步指导开发者完成本地开发环境的搭建,并提供了分片策略详解和常见问题排查方法,帮助开发者高效验证分库分表功能。

weixin_28366053的博客 214

autoreleasePool与Runloop

https://www.jianshu.com/p/0e8af0a0b4ce 一:autorelease对象的释放时机 @autoreleasepool即自动释放池,在自动释放池中声明的对象如果调用了autorelase方法,那么当@autoreleasepool代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。 转换为C++代码 int main(int argc, const char * argv[]) { /* @autoreleasepoo

qq_44865905的博客 1372

ArcGIS拓扑面错误批量修改

在工作学习中经常会遇到拓扑修改的问题,当数据量巨大的时候会产生拓扑问题 当拓扑错误数量巨大时,人工逐一修改是不现实的,基于此利用gis自带工具箱实现拓扑面类错误的批量修改。 一、.常见拓扑错误的类型: 1面重叠 2空隙(绿色部分) 二、拓扑检查 1新建数据库-----新建数据集—导入数据. 2新建拓扑 3修改拓扑容差及名称 添加拓扑修改规则 完成拓扑检查-查看拓扑错误 三、拓扑批量...

qq_43173805的博客 3万+

【华为OD机试真题 2025C卷新题】855、识文断句 | 机试真题+思路参考+代码分析(C++、Java、Py、C语言、JS)

【华为OD机试真题 2025C卷新题】855、识文断句 | 机试真题+思路参考+代码分析(C++、Java、Py、C语言、JS) 华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2025e卷,od机试,2025E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2025华为od,如何备战华为

KJ.JK 1850

Java高并发程序设计学习笔记(二)锁相关

锁的种类 Java中的锁大致分为:偏向锁,自旋锁,轻量级锁,重量级锁 锁的使用方式为:先提供偏向锁,如果不满足时,升级为轻量级锁,再不满足,升级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。锁只能升级,不能降级。 偏向锁 是一种编译解释锁。如果代码中不可能出现多线程并发争抢同一个锁的时候,JVM编译代码,解释执行的时候,会自动的放弃同步信息。消除synchronized...

qq_16694993的博客 486

安装Codex时出现无法下载此应用和Microsoft Store初始化失败的解决方案

本文主要介绍了安装Codex时出现无法下载此应用和Microsoft Store初始化失败的解决方案,希望能对使用codex的同学们有所帮助。 文章目录 1. 问题描述 2. Windows 解决方案 3. Mac解决方案

weixin_43178406的博客 7万+

Java并发编程之wait、notify和join原理

文章目录1、wait、notify介绍2、API介绍3、sleep(long n) 和 wait(long n)的区别4、wait/notify的正确使用# 2、join源码3、park & unpack3.1、基本使用3.2、 park、 unpark 原理4、线程状态转换 1、wait、notify介绍 必须要获取到锁对象, 才能调用这些方法 当线程0获得到了锁, 成为Monitor的Owner, 但是此时它发现自己想要执行synchroized代码块的条件不满足; 此时它就调用obj.wa

从基础到源码解析的学习记录 1739

prplOS amx-fxgi调试-订阅subscribe

本文分析了prplOS中amx-fxgi订阅功能(subscribe)的实现与调试过程。通过梳理订阅流程发现,默认订阅失败主要由于acl规则限制导致。文章详细介绍了订阅的两个关键步骤:建立GET长连接和POST设置订阅路径,并通过修改代码绕过acl验证成功实现订阅功能。最终验证了在同一连接下订阅多个路径及实时接收状态更新的可行性,有效降低了设备查询负载。该方案为prplOS的实时数据监控提供了优化思路。

四儿家的小祖宗的博客 329

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

【并发编程】wait/notify

obj.wait() 让进入object监视器的线程到waitSet等待。obj.notify() 在object上正在waitSet等待的线程中随机挑选一个唤醒obj.notifyAll() 让object上正在waitSet等待的线程全部唤醒他们都是线程之间进行协作的手段,都属于Object对象的方法,必须获得此对象的锁,才能调用这几个方法@Slf4jlog.debug("执行....");// 让线程在obj上一直等待下去。

m0_62645012的博客 1729

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Java并发编程:wait方法原理与最佳实践

在当今的软件开发领域,Java作为一种广泛使用的编程语言,其并发编程能力尤为重要。特别是在多线程环境下,如何有效地管理线程间的同步和数据共享,是保证系统稳定性和性能的关键。在众多并发知识点中,"wait"方法作为Java并发编程中的重要组成部分,其作用和适用场景不容忽视。想象一个场景,在一个多线程的数据库操作系统中,多个线程需要访问同一个资源,如数据库连接池。如果不对这些线程进行适当的同步,可能会导致数据不一致或资源竞争等问题。此时,"wait"方法就派上了用场。

我是Java程序员廖志伟,感谢朋友们的支持!不定期贡献一篇高品质、过万文字、图文并茂且附有视频解说、满载代码示例注释的良心之作,坚决杜绝粗制乱造。 639

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【Java并发编程系列】- wait & notify,park & unpark,ReentrantLock

而ReentrantLock支持多间休息室,有专门等烟的休息室、专门等早餐的休息室,唤醒时也是按休息室来唤醒。ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持。synchronized是那些不满足条件的线程都在一间休息室等消息。有了多个等待区,就可以避免虚假唤醒,从而简化while-wait。每个线程都有一个自己的park对象 ,该对象由。无论是先使用还是后使用,都能恢复被暂停的线程。公平锁:在线程获取锁失败,进入阻塞队列时,被打断了就不用阻塞了,直接停止运行。

m0_68383079的博客 829

Java并发编程-wait和notify原理剖析

1.小故事 - 为什么需要 wait 有一对小孩都要使用算盘CPU进行计算,为了计算过程中的计算安全,老王设计了一个synchronized锁,让每个时刻只有一个线程进入房间成为owner的主人。这样只能排队一个一个线程的来,从而保证计算过程中的线程安全。 由于条件不满足,小南不能继续进行计算(比如他必须要叼根烟,有烟才能进行计算) 但小南如果一直占用着锁,其它人就得一直阻塞,效率太低 于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,但这时锁释放开,其它人可以由

Java硬件工程师的博客 2392

TIME_WAIT状态原理(很清楚)

转自: http://elf8848.iteye.com/blog/1739571 TIME_WAIT状态原理 ---------------------------- 通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态。 客户端主动关闭连接时,会发送最后一个ack后,然后会进入TIME_WAIT状态,再停留2个MSL时间(后有MSL的解释),进入C

洛冰翼的专栏 1万+

并发编程原理与实战(四)经典并发协同方式synchronized与wait+notify详解

本文由多线程的概念引出并发协同以及对象监视器锁(synchronized)的概念,在调用wait()和notify()方法前必须先获得对象监视器锁,并讲解了synchronized的使用方法。通过两个线程交替顺序输出数字的经典题目,结合官方说明一步步分析线程如何获得锁,以及wait()和notify()的使用方法。在此我们总结出并发协同的核心思路:弄清楚并发的是什么,什么时候需要协同,谁来等待,谁来唤醒,通过这样的一个分析过程,希望能让大家深刻理解基础的并发协同的方式。

真战 1823
上一篇: spring源码系列(六)——番外篇如何编译spring的源码
下一篇: spring源码系列(七)——beanDefinition(3)
子路程序员
博客等级 码龄17年 8814粉丝 10原创
评论 67
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

子路程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值