并发编程之五:synchronized底层原理、monitor、轻量级锁、偏向锁、wati/notify/notifyAll、join、状态转换

1.深入JVM源码,查看如何实现wait(),notify()方法 1.深入JVM源码,查看如何实现wait() 在java中,每个对象都是Object的子类,java中每个对象都是"锁",但是这是如何实现的呢,今天我们看看JVM层面如何实现Object.wait/notify方法的。 在讲这个之前我们需要了解一下java的对象在内存中的布局情况; 1.对象头:下面重点讲解 2.实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐 3.填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的 阅读详情

synchronized底层原理

我们从底层原理来分析synchronized,进一步的了解它。
下面的内容会非常的枯燥和无聊,想要学习上乘武学就得耐得住枯燥、无聊和寂寞。

Monitor

学习Monitor之前我们先了解一下java对象头,后面我们会用到java对象头里的知识。

java对象头

通常我们的一个对象在内存中有2部分组成,第一部分是对象头,另一部分是对象体,它里面存储了对象中的成员变量。
对象头有两部分

  • 1、klass Word
    每个对象都有一个类型,例如student类型或者teacher类型,这个对象是怎么知道它是什么类型的呢,就是在对象头里有一个Klass word,它是一个指针指向了这个对象所从属的class(即通过klass word找到它的类对象)。
  • 2、Mark Word,它里面就放了很多的其它信息,比如对象的哈希值(hashCode)。还有一个垃圾回收器用到的age是jvm里的东西,这里我们不做深入研究

在这里插入图片描述
在这里插入图片描述

monitor(锁)

monitor是java里一个重要的概念,也是synchronized的一个底层原理。
Monitor被翻译为监视器或者管程,(咱们可以把他理解为之前提到的锁,就是方法加了synchronized的时候会给对象加锁,这个锁可以理解为指得就是monitor)
每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头
的Mark Word中就被设置指向Monitor对象的指针。于是java对象就和monitor关联上了(所以我们不管是将synchronized加在方法上还是加在静态方法上,原理都是加在了对象上)。
加锁过程:例如当一个线程执行一个添加了synchronized的方法的时候,它就会尝试把obj(java对象)对象,与操作系统提供的Monitor相关联(操作系统提供的,我们在jav中看不到它的相关表示),它就是靠一个指针地址执行操作系统的monitor。相当于在obj的对象头的MarkWord记录了这个操作地址,将来指向这个对象。然后线程1就来执行这个代码了,然后它一检查这monitor的owner已经有对象了,monirot只能有一个拥有者。但是呢Thread1也会和这个monirt来进行关联,此时它就通过Monirto里的一个EntryList(可以把它理解为一个等待队列或者阻塞队列),然后呢线程1就从运行状态进入了阻塞队列进入了阻塞状态(blocked),其它线程过来也是和线程1一样的过程。那么啥时候线程1、3会执行呢,就是线程2执行完毕,并且线程2释放了Monitor的拥有权(就是我们说的释放锁资源),于是此monitor的owner就空出来了,此时会通知monitor里的entryList中阻塞的线程,把他们全部叫醒,叫醒之后他们才回去竞争然后成为下一Owner,才能继续执行。以上就是我们的monitro和我们的锁的关系,他其实就是充当了锁的角色,我们的对象总是和一个monitor向关联,只要是同一个对象他就会和同一个monitro向关联,如果是另一个对象那么它关联的就是另外一个monirot,就达不到锁的效果了(这也就解释了,我们的synchronized为啥必须的是锁住同一个对象,才能起作用了)。以上过程如下两图所示

在这里插入图片描述
在这里插入图片描述

■刚开始Monitor中Owner为null
■当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2, Monitor中只能有一一个
Owner
■在Thread-2. 上锁的过程中,如果Thread-3, Thread-4, Thread-5 也来执行synchronized(obj), 就会进入
EntryList BLOCKED
■Thread-2 执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争的时是非公平的
■图中WaitSet中的Thread-0, Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,后面
讲wait-notify时会分析
注意:
■synchronized必须是进入同一一个对象的monitor才有上述的效果
■不加synchronized的对象不会关联监视器,不遵从以上规则。(也就是不加锁的代码不会有什么同步啥的,就是普通的代码)

synchronized原理进阶

上节我们知道synchronized原理里的锁实际上是monitro,但是monitor是操作系统所提供的,要使用它成本是比较高的,如果我们每次程序使用synchronized都要获取一个monitor的锁的话,对我们的程序性能是有影响的。从java6开始对获取锁的方式进行了改进优化,从使用monitor锁,改进了可以使用轻量级锁、偏量锁。

小故事

小故事
故事角色
■老王-操作系统
■小南-线程
■小女-线程
■房间-对象
■房间门上-防盗锁- Monitor
■房间门上-小南书包-轻量级锁
■房间门上-刻上小南大名-偏向锁
■批量重刻名-一个类的偏向锁撤销到达20阈值
■不能刻名字-批量撤销该类对象的偏向锁,设置该类不可偏向
小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁(monitor),当cpu时间片用完了上下文切换时,锁住门。这样,即使他离开了,别人也进不了门,他的工作就是安全的。
但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天
用,小女晚上用。每次上锁太麻烦了,有没有更简单的办法呢?
【轻量级锁】小南和小女商量了-下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一
样,因此每次进前得翻翻书包【轻量级锁】,看课本是谁的,如果是自己的,那么就可以进门,这样省的上锁解锁了。
万一-书包不是自己的,那么就在门]外等,并通知对方下次用锁1门的方式。(轻量级锁,性能是会得到提升的)
后来,小女回老家了,很长一段时间都不会用这个房间。 小南每次还是挂书包,翻书包,虽然比锁门省事
了,但仍然觉得麻烦。
【偏向锁】于是,小南干脆在门上刻上了自己的名字【偏向锁】: [小南专属房间, 其它人勿用],下次来用房间时,只要名字还在,那么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小
南刻的名字擦掉,升级为挂书包的方式。(偏向锁:房间专属于某个线程使用,偏向于某个线程,这种锁叫偏向锁,偏向锁的优化:如果有人和它竞争,那么偏向锁会被撤销掉,然后变成更进一步的轻量级锁的方式)
同学们都放假回老家了,小南就膨胀了,在20个房间刻上了自己的名字,想进哪个进哪个。后来他自己放
假回老家了,这时小女回来了(她也要用这些房间), 结果就是得一个个地擦掉小南刻的名字,升级为挂书
包的方式。老王(JVM)觉得这成本有点高,提出了一种批量重刻名的方法,他让小女不用挂书包了,可以直接在门
上刻上自己的名字.(这种情况当偏向锁被撤销达到一定的阈值的时候(20),jvm在这种情况就会认为,我不应该让这些对象专属让某个线程去使用了,也得让这些对象有机会被其它线程所使用,这种被称之为:批量重偏向)

后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名了,只能挂书包.(当偏向锁撤销阈值达到4
的时候,jvm认为该类不适合做这些优化,即该类不适合被线程用来进行当前的工作,这就是将偏向锁设置为不可偏向)

轻量级锁

使用轻量级锁的原因是提升效率,重量级锁Monitor每次每个对象都会和一个Monitor向关联,相率太慢。线程栈中的锁记录去充当锁,比monitor来说提升了性能。
故事讲完了,我们看下它门的底层实现。
1、轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的( 也就是没有竞
争,就是一个线程对一个对象加锁,解锁的这个过程中没有其它线程去获取该锁资源。等该线程将锁资源释放之后,另一个线程才去获取锁资源,对该对象进行加锁解锁),那么可以使用轻量级锁来优化。这种事没有竞争的情况,如果有竞争则会升级成重量级锁。
2、轻量级锁对使用者是透明的,即语法仍然是synchronized(它会先用轻量级锁,如果轻量级锁加锁失败,它才会用重量级锁)
3、假设有两个方法同步块,利用同一个对象加锁
在这里插入图片描述

下面我们来分析以下以上代码的加锁过程

  1. 在线程调用方法的时候都会创建一个栈帧。先在栈帧里创建锁记录(Lock Record)
    对象(jvm层面的,对于我们是不可见的),每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word。

    lock Record分为两部分
    1)、lock record地址:记录对象的mark word(存储将来要加锁对象的mark word.)
    2)、Object reference: 对象指针(将来锁住对象的时候的对象地址)
    在这里插入图片描述

  2. 让锁记录中Object reference指向锁对象,并尝试用cas(一个原子操作)替换Object的Mark Word, 将Mark Word的值存入 锁记录(将object里的哈希值等和markword做一个交换)
    在这里插入图片描述

  3. 如果cas(是一个原子操作)替换成功,对象头中存储了锁记录地址和状态00(表示加的是轻量级锁), 表示由该线程给对象加锁,而多对象里面呢就把对象的哈希码,分三年龄等存储在锁记录里(lock Record),将来解锁的时候可以再给它恢复过去。这时图示如下
    在这里插入图片描述
    怎么知道加锁成功了呢,mark word里有标记为,如下如所示
    在这里插入图片描述

  4. .如果cas失败,有两种情况
    ■如果是其它线程已经持有了该Object的轻量级锁(对象头的锁码为00),这时表明有竞争,进入锁膨胀过程
    ■如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数.(锁重入自己又给自己加了一个锁,但是加锁的时候对象的状态已经是00了,所以加锁失败,然后它知道这是自己给自加锁了,然后再添加一条Lock Record作为重入的计数,然后新加的lock record为null,它就是以lock Record的个数来确定加了几次锁,有几个lock Record就加了几次锁)
    在这里插入图片描述
    上面的4步为加锁,下面我们来看解锁的操作

  5. 当退出synchronized代码块(解锁时)如果有取值为nul的锁记录,表示有重入,这时重置锁记录,表示 重入计数减一
    在这里插入图片描述
    ■当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象
    头,将哈希值,和01还原过去。(这个过程就是代码方法1执行完毕释放锁资源的场景)
    ■成功,则解锁成功
    ■失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

膨胀锁

如果在尝试加轻量级锁的过程中,CAS(一种原子操作)操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀即:将轻量级锁变为重量级锁。

如下代码
在这里插入图片描述

■当Thread-1 进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁
在这里插入图片描述
■这时Thread-I加轻量级锁失败,进入锁膨胀流程
■即为Object对象申请Monitor锁,让Object指向重量级锁地址。(为啥申请monitor,因为线程1申请加锁,但是锁已经被其它线程使用,然后就加锁失败,然后就只能等待资源锁被释放了,所以你的进入monitor锁去等待,就是阻塞状态,轻量级锁没有阻塞状态的说法,所以到重量级锁才有阻塞。申请monitor锁之后呢,object就不能再记录轻量级锁的信息了,它就指向了monitor(重量级锁的地址),锁码也会变成10)
■然后线程就进入Monitor的EntryList BLOCKED(其实就是阻塞状态)

■当Thread-0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败(因为它在上一步就指向了重量级锁的地址而不是轻量级锁了,所以会解锁失败(解锁就是将轻量级锁与objject的信息再次互换还原))。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED线程
在这里插入图片描述

自旋优化

自旋:避免阻塞,线程上下文切换的发生(知此一言,可以不看下面一段废话)。
缺点,它会使用cpu,多核的计算机比较好一点,如果你只有一核cpu,cpu正在执行线程1,它需要在线程1执行的同时去占用cpu去执行自旋,所以这种情况自选优化就没有啥意义了,所以自旋一定是多核cpu下才有意义。
什么时候会使用这个自旋优化呢?在上一节我们说到当一个线程去获取锁的时候发现monitor已经拥有owner了,另外的线程就会进入entryList阻塞(重量级锁竞争的时候),就是此时进入了这个相关的优化。
重量级锁竞争的时候,还可以使用自旋来进行优化(其实就是在重量级锁竞争的时候发现monitor已经有owner的时候不会先进入阻塞而是进行几次循环重试去获取锁),如果当前线程自旋(循环中占用锁的线程释放了锁)成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以成为了owner就不用阻塞了。(其实就是在重量级锁竞争的时候发现monitor已经有owner的时候不会先进入阻塞而是进入一个循环)。所以当前线程就可以避免阻塞了。因为阻塞要发生一次线程的上下文切换,是比较慢,比较耗费性能的。如下图所示。
在这里插入图片描述
自选失败:自旋不成功,那就进入monitor锁,阻塞了。
在这里插入图片描述
·在Java 6之后自旋锁是自适应的,比如对象刚刚的一次目旋操作成功过,那么认为这次目晓D功的可能任会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
·自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势(避免阻塞)。
.Java 7之后不能控制是否开启自旋功能

偏向锁

定义:轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS(简单的理解为是一系列的原子操作)操作。
Java 6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。

什么时重入呢(就是俄罗斯套娃 )?例如以下代码,线程a对调用m1并对其加锁,但是m1还没执行完,就调用m2然后再对m2中的代码进行加锁,然后m2没执行完又对加锁,,,虽然后面两次加锁是成功的 ,但是每次操作都要做一次cas检查。
在这里插入图片描述

下面我们来看一下轻量级锁与偏向锁的区别
我们提到轻量级锁其实是对重量级锁(monitor)的优化,那么偏向锁就是对轻量级锁的进一步优化。
以下的轻量级锁类比于我们上面提到的翻书包的形式。
在这里插入图片描述
对轻量级锁的进一步优化方式(轻量级锁的cas也是耗性能的):不用翻书包了,把名字刻在门上,直接看名字就可以了。
定义:
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS(简单的理解为是一系列的原子操作)操作。
Java 6中引入了偏向锁来做进一步优化:只有第一次使用CAS将线程ID(名字)设置到对象(门)的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归该线程所有。
在这里插入图片描述

1.偏向状态

回忆一下对象头的Mark Word格式,注意下图锁的状态以及对应的状态码,如Heavyweight Locked(重量级锁)状态码: 10
在这里插入图片描述

在这里插入图片描述
一个对象创建时:

  • 1、如果开启了偏向锁(默认开启),那么对象创建后,markword值为OxO5即最后3位为101,这时它的thread、epoch、age都为О
  • 2、偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数
    –XX:BiasedLockingstartupDelay=0来禁用延迟
  • 3、如果没有开启偏向锁,那么对象创建后,markword值为Ox01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值。

代码验证:
我们用代码验证一下开启了偏向锁对象的最后三位是否为001
1、添加依赖
在这里插入图片描述

1.1测试偏向锁的延迟特性

在这里插入图片描述
在这里插入图片描述

我们在上面的markword表中发现001是正常状态。但是我们上面说道默认是开启了偏向锁的,按理来说这个对象是101而不是001呢?因为偏向锁默认是延迟的,上面第二条。
我们下面将线程睡眠4s,然后再创建一个对象
在这里插入图片描述

在这里插入图片描述
刚一开始偏向锁没有生效所以是001,后面生效了所以是101.
每次都要让线程睡4s,比较麻烦:添加虚拟机参数: –XX:BiasedLockingstartupDelay=0
在这里插入图片描述
在这里插入图片描述

1.2测试偏向锁

上面的101只是启用了偏向锁,但是还没有加锁。怎么加偏向锁呢?
代码里添加synchronized,它就会优先添加偏向锁而不是重量级锁或者轻量级锁
在这里插入图片描述
打印结果分别为:
加锁前:101,表示启用了偏向锁。
加锁后:101,但是前面还多了一些东西,就是线程id(从右往左第10位(从1开始数)之后的就是线程id)。注意该id与java中线程getID的到的值是不一样的。这里的id是操作系统为线程设置的唯一标识。与java中的线程对象不是一一对应的。
解锁后(释放完锁):解锁后与加锁后没啥变化?这就是所谓的偏向,这里的dog对象以后就给这个线程用了(此处为main方法,所以线程为主线程)因为主线程一上来给它加了锁了,以后的d对象就从属与主线程了,它的mark word头里存储的始终就是主线程的线程id,除非其他线程使用了这个对象它才会改变或者其他的条件发生时它才会改变。这也是偏向锁的得名原因
在这里插入图片描述

1.3测试禁用偏向锁

偏向使用的场景是冲突很少的情况使用,比如我就一个线程去使用对象给对象加锁,这种场景比较适合使用偏向锁。但是如果程序里,一个对象的使用场景就是多线程它要去竞争去访问对象,这个时候偏向锁就不合适了,我们可以用一个参数把偏向锁给禁用掉,让它一上来就是正常状态(Normal),而不是Biased(可偏向的状态)
在上面测试代码运行时在添加VM参数 -xx:-UseBiasedLocking禁用偏向锁(U前面的-代表禁用偏向锁,+表示启用偏向锁)。这行命令相当于是一个开关。
在这里插入图片描述
代码都没变只不过是在配置中把偏向锁给禁用了。
在这里插入图片描述
在这里插入图片描述
打印结果分别为:
加锁前:001,表示未启用偏向锁。
加锁后:000,后两位为00,是轻量级锁(看上面的mark word结构图),它前面的剩余62位就是轻量级锁的记录地址指针(看上面的mark word结构图)。重量级锁优化到轻量级锁,所以轻量级锁就不优化了,因为偏向锁被禁用了,仍然是轻量级锁。
解锁后(释放完锁):解锁后又恢复到001,未加锁的状态。
结论:
所以我们看到禁用了偏向锁,它上来用的直接就是轻量级锁。所以我们从优先级上划分:有偏向级锁,使用偏向级锁,如果其他线程使用了这个对象它就会撤销偏向锁,变成轻量级锁,如果轻量级锁有竞争,比如线程1加了轻量级锁,此时线程2来竞争了,这时候就会锁膨胀变成重量级锁。
所以优先顺序: 偏向锁> 轻量级锁>重量级锁

1.4测试hashCode

测试内容,
首先确认我们的偏向锁是打开的。然后我们在使用偏向锁之前我们调用一下hashcode。这个操作很诡异,它会禁用到偏向锁。

打开偏向锁
在这里插入图片描述

在这里插入图片描述
打印结果分别为:
加锁前:按理来说应该是101(启用偏向锁,但是这里却是001即Normal正常状态,未启用偏向锁)
加锁后:加下来加的锁也不是偏向锁了而是:000,后两位为00,是轻量级锁(看上面的mark word结构图),即从正常状态变成了轻量级锁而非偏向锁。
解锁后(释放完锁):解锁后又恢复到001,未加锁的状态即正常状态。在这里插入图片描述
上述结果的第一行从右往左(从1开始数),第9位开始就全都是hash码。这里的hash码呢,用的时候才会产生,其余情况都为0。当你第一次调用hashCode的时候它就会产生哈希码,并且将哈希码填充到对象头的mark word里面。那为什么调用一下哈希码就会禁用到对象的偏向锁呢?因为如果对象处于偏向锁的状态那么mark word最多存储一个偏向锁的线程id如下图。在想存31位的hashcode就存不下了。所以当一个可偏向的对象调用它的hashCode方法以后,它就会撤销这个对象的偏向状态(状态码由101变成001)然后把的线程ID啥的都给清掉,然后将hashCode存储到对象头的mark word里面,因为它没地方存储哈希码。
所以我们要记住,调用hashCode之后它就会由偏向状态变为正常状态。
那为什么我们的轻量级锁、重量级锁调用hashCode之后不会有这个问题呢?
是因为轻量级锁的hashCode会存储在线程栈帧的锁记录里。
重量级锁的哈希码会存储在monitor对象里。
轻量级锁和重量级锁将来解锁的时候还会把哈希码还原回来。但是偏向锁它没有额外的存储空间了,所以调用hashCode之后会让偏向状态禁用,从而变成不可偏向的正常状态。
在这里插入图片描述

2.撤销偏向锁

2.1调用该对象的hashCode方法

见上小节

2.2其它线程使用对象

当有其它线程使用偏向锁时,会将偏向锁升级为轻量级锁,于是偏向锁也就撤销了。
场景:如果线程1已经给对象加锁了,相当于该对象是偏向于线程1的,但是后来呢有一个其它线程也想用这个对象,那就跟偏向的本意就违背了,因为本来是说这个对象是线程1专用的,结果又来个线程2要使用这个对象,此时它也会撤销对象的偏向状态,将对象的状态从可偏向变成不可偏向,最后偏向锁也会升级为轻量级锁。
要演示效果,必须得让两个线程交错开,必须是线程1把锁解开了,线程2再去加锁,如果没有交错就不是轻量级锁了,那就是重量级锁了。偏向锁和轻量级锁有个前提就是访问对象的时候,两个线程是错开的。

如下代码:一上来2个线程都运行,但是t2线程是陷入等待。因为t1线程线运行,此时测试类的类对象已经被加锁,t2线程陷入等待队列,然后t1线程执行完毕时候再通知这个测试类的类对象上的等待线程,将他门唤醒,因为这里只有一个t2线程等待所以我们用了notify,用notifyAll也是可以的。所以此时t1与t2线程就是错开的。
注意:这里不锁d对象,它是我们的测试对象,我们锁这个测试类的类对象。

在这里插入图片描述
运行结果:
线程1对d对象加的是偏向锁
线程2加的是轻量级锁。
验证本节开头的结论:即当有其它线程使用偏向锁时,会将偏向锁升级为轻量级锁。
在这里插入图片描述

2.3调用wait/notify

为什么调用wati/notify之后会撤销偏向锁呢?因为wait和notify只有重量级锁才有,所以当用到wait与notify这种机制的时候只有重量锁才有,所以调用以上两个方法的时候它肯定会将轻量级锁或者偏向锁升级为重量级锁

2.4批量重偏向

在本章节的第二小节,其他线程使用对象时,我们说道,可偏向的对象被两个线程访问或被多个线程访问时,就会导致它的偏向状态被改变,即从可偏向变为不可偏向。其实这就叫做撤销偏向。撤销偏向对性能的损耗有些大。因为我们第二小节的场景是,当一个对象被一个线程用完了,其他线程采取使用,他们是没有竞争的,这个时候才会出现撤销偏向锁的这个场景。
所以呢,批量重偏向基于这么一个场景进行优化:
如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID
当撤销偏向锁阈值超过20次后(可简单理解为一只从t1偏向至t2,偏向了很多次),jvm会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。而不是撤销然后升级为轻量级锁,因为撤销也是耗费性能的。而且是批量的把这些对象重偏向到其他线程。
如下代码:
第一个for循环中加锁是为了让这30个对象都先偏向t1,接着线程2就开始运行了,线程2会一开始在list对象上等待。等到线程1把这30个对象加锁加完了,线程1就会notify唤醒list,然后notify就会让我们的县城向下运行,线程2要再对这30个对象加锁。当线程2里的for循环第一次给对象加锁的时候,因为list里的对象原先都是偏向t1,当线程2第一次将他加锁的时候,它会将偏向锁撤销掉升级为轻量级锁。但是这样的循环次数比较多了,也就是撤销偏向锁的次数比较多的时候,超过了一个阈值(默认是20),jvm就会认为它对象偏向给t1偏向错了,jvm就会把剩下的对象重新批量的偏向给t2。
在这里插入图片描述
在这里插入图片描述
控制台打印前30条为t1线程:给这30个对象加偏向锁(后3位为101)到t1
在这里插入图片描述
t2线程的结果前18条都是对象被撤销然后添加上轻量级锁。
在这里插入图片描述
但是t2从第20(打印索引从第0个开始打印,所以第19个索引对对应的就是第20个对象)个对象开始又变成101了,也就是加了偏向锁。
但是这个锁它加在哪个线程上了呢?我们可以对比一下线程id,后3位是锁的状态码,其余的就是线程的id。我们拿线程1的打印结果的县城id与线程2的打印结果的线程id对比发现是不同的。所以批量重偏向,不是撤销然后升级为轻量级锁,因为撤销也是耗费性能的。而且是批量的把这些对象重偏向到其他线程。
在这里插入图片描述

2.5批量撤销

当撤销偏向锁阈值超过40次后,jvm 会这样觉得,这个类的竞争非常的激烈,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。
例如上边的例子,我们在线程2执行完之后再来个线程3,于是到第40次撤销偏向锁之后,该对象就不可偏向了。

3.锁消除

wait/notify

小故事,为什么需要wait?

在这里插入图片描述
在这里插入图片描述

拥有锁资源但是,又不满足执行条件的线程,不能总是占这茅坑(cpu)不拉屎,于是他们就可以调用wait方法去等待(酝酿一下感觉)。
结论1:线程调用wait会释放当前线程所拥有的锁资源。
结论2:当其他线程执行notify/notifyAll方法后wait中的线程才会被唤醒,才有资格去重新竞争资源锁。

在这里插入图片描述

wait/notify底层原理、相同点、不同点

在这里插入图片描述

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

注意1:waiting是无限的等待,直到被唤醒。而timewait是有时限的等待。
注意2:
wait与blocked的区别1
wait:已经获得过锁(已经是owner了)但是又放弃了锁,在Monitor的WaitSet等待的线程。
blocked:还没有获得过锁资源并且正在等待锁的线程。这些线程在Monitor的EntryList中等待。
wait与blocked的区别2:唤醒机制不同
WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味者立刻获得锁仍需进入EntryList重新竞争。
BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片。
wait与blocked的相同点:BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片。cpu在调度的时候也不会考虑它们。

wait/notify(API)

api介绍:
wait、notify、notifyAll方法是Object类中的方法。

  1. obj .wait()让进入object监视器(其实就是获取到锁资源的线程)的线程到waitSet等待。
  2. obj.notify()在object.上正在waitSet等待的线程中挑一个唤醒。
  3. obj.notifyAll()让object. 上正在waitSet等待的线程全部唤醒。
  4. 它们都是线程之间进行协作的手段,都属于Object对象的方法。必须获得此对象的锁,才能调用这几个方法(你必须先成为了owner你才有资格进入waitset)。这个就是我们上一节中提到的wait与block的区别之一,我们再来复习一下:
    wait:已经获得过锁(已经是owner了)但是又放弃了锁,在Monitor的WaitSet等待的线程。
    blocked:还没有获得过锁资源并且正在等待锁的线程。这些线程在Monitor的EntryList中等待。
    notify/notifyAll也是只能唤醒在wait状态下的线程,是叫不到EntryList里的(blocked)线程的。
    也就是说, notify/notifyAll只能唤醒wait状态下的线程对blocked状态下的线程毫无作用。所以不管是wait还是notify/notifyAll,他们都有一个前提就是线程都必须获得了对象锁。成为了owner之后才能调用这3个方法。例如成为owner之后你可以自己调用wait让自己等待,或者调用notify去唤醒其他的wait状态下的线程。

测试1:
一下代码为啥会有异常。
在这里插入图片描述

在这里插入图片描述
以上异常是为什么呢?因为你这个线程(main主线程),它根本就没有获得锁,也就无法将线程转为wait状态。

所以我们的修改一下上面的代码,先让线程获得锁,然后再将它转为wait状态。我们先synchronized的让线程获得该对象的对象锁,成为了owner,然后再调用对象锁。就进入了lock所关联的Monitor对象的waitSet里去等待。
在这里插入图片描述
在这里插入图片描述
代码为什么没有运行结束呢?因为主线程已经进入了wait状态了,它再等,等其它拥有线程锁的线程唤醒它。唤醒后也不会立刻去执行,而是重新去竞争资源锁。
同理notify/nofifyAll也是同样的情况,报的错误也是一样的。

测试2:notify与notifyAll的区别

/**
 * @Author: llb
 * @Date: 2021/4/16 18:26
 */
public class WaitTest2 {
    private static Object obj = new Object();
    public static void main(String[] args) throws Exception{

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("执行了线程1");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("你太老了,关某大刀,不斩老幼");
                }
            }
        },"关二爷").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    System.out.println("执行了线程2");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("哼哼,三姓家奴,看枪");
                }
            }
        },"张翼德").start();

        Thread.sleep(2000);
        System.out.println("唤醒线程");
        // 这里主线程肯定是可以获取到该锁的,
        // 因为其它两个线程都获取过锁,并且执行了wait方法,它俩都去休息室等待去了
        // wait执行后会释放锁资源
        synchronized (obj) {
            // 注意这里的线程1、线程2用的都是同一个锁对象。
            // 这里呢它就找到obj的monitor找到它的休息室,
            // 如果是notify那就随机唤醒obj上(休息室里wait)wait的一个线程
            // 如果是notifyAll那就唤醒obj上(休息室里的)所有wait线程
            obj.notify();
        }
    }
}

notify打印结果:
在这里插入图片描述
notifyAll打印结果
在这里插入图片描述
结论:
如果是notify那就随机唤醒obj(被加锁的对象)上(休息室里wait)wait的一个线程。
如果是notifyAll那就随机唤醒obj(被加锁的对象)上(休息室里wait)wait的一个线程。

测试3:带时间的wait方法
我们看一下Object的源码
在这里插入图片描述

wait无参方法:
我们可以看到无参的wait方法,实际上是调用了一个参数为0的wait方法。这里的0的含义就是无限制的等待下去。方法上也有描述,其它线程使用notify/notifyAll方法去唤醒它。
在这里插入图片描述

wait的有时限方法
wiat有时限的方法,等待指定的时间。如果在指定的时间之后如果没有线程把它唤醒,它就会自动结束等待。然后继续向下执行。如果在等待的过程中它被其它线程唤醒了,那么它就会自动的去执行下面的代码,而wait(timeout就不会在执行了)。
在这里插入图片描述
有两个参数的时限方法
在这里插入图片描述
其实就是纳秒的范围,如果不再范围则抛出异常,否则就把秒数+1s,用的还是有时限的wait(timeout)方法。

wait vs sleep

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

  1. sleep是 Thread方法,而wait是 Object的方法
  2. sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用(例如线程如果没有获得锁就调用wait会抛出异常的)
  3. sleep在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。(也就是说如果一个线程获得了锁,并且sleep了,且sleep的时间没到,那么其他线程是不会获的锁的。但是wait就不是,线程一点wait了,那么它会释放它所占有的锁资源,当它再wait状态的时候,其它线程可以得到锁)。
  4. 共同点:他们的状态都是一样的都是:TIMED_WAITING(有时限的等待)
    如下代码,主线程在20s内是得不到锁的
    在这里插入图片描述
    如下代码,主线程是可能会获取到锁的
    在这里插入图片描述

wait/notify正确使用姿势

实战是检验真理的唯一标准,我们来实战一下哪种姿势才是正确的。
接下来我们分5步,来逐渐改善我们的代码。
背景:一些线程他们要使用一个共享的房间来达到一个线程安全的目的。所以他们都要使用加锁的方式去进入到room房间去做些线程安全的代码。
前提条件:
人物 小南:正所谓,一根烟,一杯酒,一个bug调一宿,小南就是典型的有烟才能干活。所以小南的在有烟的前提下才能去干活。
下面我们就用一段代码来模拟这个过程。

step1(sleep):

如下代码

public class WaitTest {

    static final Object room = new Object();
    static boolean hasCigarette = false;// 有没有烟
    static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    logger.log("有烟没? " + hasCigarette);
                    if (!hasCigarette) {
                        logger.log("没烟先歇会: " + hasCigarette);
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    logger.log("有烟没? " + hasCigarette);
                    if (hasCigarette) {
                        logger.log(Thread.currentThread().getName() + ":可以干活了: " + hasCigarette);
                    }
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (room) {
                        logger.log(Thread.currentThread().getName() +":可以干活了 " + hasCigarette);
                    }
                }
            },"其他人").start();
        }
        Thread.sleep(1);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 下面的代码可以加锁吗?
                hasCigarette = true;
                logger.log(Thread.currentThread().getName() +":烟到了 " + hasCigarette);
            }
        },"送烟的").start();
    }
}

运行结果:
在这里插入图片描述

代码分析:以上一共7个线程,其中当没有烟的时候小南用的是sleep方法,该方法不会释放锁资源,所以当小南没有烟的时候其他线程也都无法运行。其次,因为锁在小南手里,只有当小南将代码执行完毕,将所资源释放后,其他线程才能去得到锁,于是就是上面的打印输出结果。

以上代码问题1:送烟的线程虽然在1s之后就把烟送到了,但是小南还是睡足了2s才醒过来。(优化方法,可以使用interrupt把睡眠中的线程叫醒,只要小南的这个线程捕捉到异常它就可以继续向下执行)
更严重的问题2:在小南睡觉的时候其他线程并没有干活,因为小南锁住了房间,并且sleep,没有释放资源。所以其他线程只能等待小南干完了,他们再继续执行。显然这样的效率非常的低。
严重的问题3:送烟的线程可以加synchronized吗?加了synchronized (room)后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main没加synchronized就好像main线程是翻窗户进来的。所以送烟的线程不能加synchronized。
如果送烟的线程也加了synchronized,相当于小南根本就没有干活。
在这里插入图片描述

使用wait-notify机制解决上面的问题。

step2代码改进(wait-notify):
public class WaitTest2 {

    static final Object room = new Object();
    static boolean hasCigarette = false;// 有没有烟
    static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    logger.log("有烟没? " + hasCigarette);
                    if (!hasCigarette) {
                        logger.log("没烟先歇会: " + hasCigarette);
                        try {
                            room.wait(2000);
                        } catch (InterruptedException e) {
                            // 该异常什么时候会抛出呢?就是其他线程调用interrupt方法
                            // 它也会让正在wait的线程被打断,打断之后,我们这边接到异常
                            // 就知道该线程被打断了,也可以对打断标记进行判断,于是可以继续进行处理。
                            // 当然了,更合理的方法是其他线程调用notify方法来唤醒
                            // 正在wait的线程
                            e.printStackTrace();
                        }
                    }
                    logger.log("有烟没? " + hasCigarette);
                    if (hasCigarette) {
                        logger.log(Thread.currentThread().getName() + ":可以干活了: " + hasCigarette);
                    }
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (room) {
                        logger.log(Thread.currentThread().getName() +":可以干活了 " + hasCigarette);
                    }
                }
            },"其他人").start();
        }
        Thread.sleep(1000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 下面的代码可以加锁吗?
                synchronized (room) {
                    hasCigarette = true;
                    logger.log(Thread.currentThread().getName() +":烟到了 " + hasCigarette);
                    // 这里要要注意,
                    // notify/notifyAll只能唤醒wait状态下的线程
                    // 对blocked状态下的线程毫无作用。
                    // 所以不管是wait还是notify/notifyAll,
                    // 他们都有一个前提就是线程都必须获得了对象锁。成为了owner之后才能调用这3个方法
                    // 所以这里要在synchronized代码块里调用notify,否则将抛出非法状态异常
                    room.notify();
                }
            }
        },"送烟的").start();
    }
}

运行结果
在这里插入图片描述
结果分析:小南得到锁,但是没有烟,于是调用wait方法(该方法会释放锁资源)所以此时小南休息不会影响其他线程干活。于是其他线程开始竞争并获得锁资源,其它线程没有烟也可以运行。于是其他线程运行完了,然后小南直到等到烟到了,然后被送烟的唤醒notify,然后,小南重新竞争资源锁,然后获取到锁之后开始从它的room.wait代码之后开始运行。

分析:进步1:可以让其他线程同时运行,小南线程不会占用锁,并发的效率得到了提升。
问题1:如果小南等待的同时也有其他的线程在等待,那送烟的线程在调用notify的时候会不会错误的调用其他线程呢?下面我们继续来优化。

step3代码改进(wait-notify、notifyAll):
public class WaitTest3 {

    static final Object room = new Object();
    static boolean hasCigarette = false;// 有没有烟
    static boolean hasTakeout = false; // 外卖
    // 虚假唤醒
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    logger.log(Thread.currentThread().getName() + ":有烟没? " + hasCigarette);
                    if (!hasCigarette) {
                        logger.log(Thread.currentThread().getName() + ":没烟先歇会: " + hasCigarette);
                        try {
                            room.wait();
                        } catch (InterruptedException e) {
                            // 该异常什么时候会抛出呢?就是其他线程调用interrupt方法
                            // 它也会让正在wait的线程被打断,打断之后,我们这边接到异常
                            // 就知道该线程被打断了,也可以对打断标记进行判断,于是可以继续进行处理。
                            // 当然了,更合理的方法是其他线程调用notify方法来唤醒
                            // 正在wait的线程
                            e.printStackTrace();
                        }
                    }
                    logger.log(Thread.currentThread().getName() + ":有烟没? " + hasCigarette);
                    if (hasCigarette) {
                        logger.log(Thread.currentThread().getName() + ":可以干活了: " + hasCigarette);
                    } else {
                        logger.log(Thread.currentThread().getName() + ":没干成活: " + hasCigarette);
                    }
                }
            }
        }, "小南").start();

        // 该线程与小南线程几乎一样,但是它等的是外卖而不是烟
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    logger.log(Thread.currentThread().getName() + ":外面送到没? " + hasTakeout);
                    if (!hasTakeout) {
                        logger.log(Thread.currentThread().getName() + ":没等待外卖先歇会: " + hasTakeout);
                        try {
                            room.wait();
                        } catch (InterruptedException e) {
                            // 该异常什么时候会抛出呢?就是其他线程调用interrupt方法
                            // 它也会让正在wait的线程被打断,打断之后,我们这边接到异常
                            // 就知道该线程被打断了,也可以对打断标记进行判断,于是可以继续进行处理。
                            // 当然了,更合理的方法是其他线程调用notify方法来唤醒
                            // 正在wait的线程
                            e.printStackTrace();
                        }
                    }
                    logger.log(Thread.currentThread().getName() + ":外面送到没? " + hasTakeout);
                    if (hasTakeout) {
                        logger.log(Thread.currentThread().getName() + ":可以干饭了: " + hasTakeout);
                    } else {
                        logger.log(Thread.currentThread().getName() + ":没干成饭: " + hasTakeout);
                    }
                }
            }
        }, "小女").start();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (room) {
                        logger.log(Thread.currentThread().getName() +":可以干活了 " + hasCigarette);
                    }
                }
            },"其他人").start();
        }
        Thread.sleep(1000);

        // 该送外卖的线程到底将谁叫醒了呢?
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    hasTakeout = true;
                    logger.log(Thread.currentThread().getName() +":外卖到了 " + hasTakeout);
                    // 这里要要注意,
                    // notify/notifyAll只能唤醒wait状态下的线程
                    // 对blocked状态下的线程毫无作用。
                    // 所以不管是wait还是notify/notifyAll,
                    // 他们都有一个前提就是线程都必须获得了对象锁。成为了owner之后才能调用这3个方法
                    // 所以这里要在synchronized代码块里调用notify,否则将抛出非法状态异常
                    room.notify();
                }
            }
        },"送外卖的").start();
    }
}

运行结果:
在这里插入图片描述
结果分析:小南,小女都没有满足条件吃或者吸烟,于是他们就都wait歇会,此时锁被释放,于是其他线程可以竞争锁,并且运行。
注意notify它只能随机的唤醒所有处于wait等待的一个线程。以上结果表明它随机的唤醒了小南,于是小南就从自己的room.wait之后的代码开始运行,但是它是吧外卖的标记置为true了,于是小南仍旧不满足干活条件。于是小南没有干成活,一直在room的wait中摸鱼。所以小南是被错误唤醒了,我们也称之为虚假唤醒。
解决方案:以上代码中使用notifyAll来代替notify,它会唤醒所有正在等待的线程(前提是同一个room里,也就是同一把对象锁)。如下代码,小南、小女都被唤醒,小女可以干饭,小南因没有烟,没有干成活(说明被唤醒了)
在这里插入图片描述
结论:我们使用notifyAll去代替notify可以解决一部分线程虚假问题。
问题:小女是干成饭了,但是小南还是没有干成活,并且被唤醒了还,也就是说仍然有线程被唤醒了,但是又不满足线程的运行条件执行不了正确的逻辑。

step4代码改进(while代替if):
public class WaitTest4 {

    static final Object room = new Object();
    static boolean hasCigarette = false;// 有没有烟
    static boolean hasTakeout = false; // 外卖
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    logger.log(Thread.currentThread().getName() + ":有烟没? " + hasCigarette);
                    while (!hasCigarette) {
                        logger.log(Thread.currentThread().getName() + ":没烟先歇会: " + hasCigarette);
                        try {
                            room.wait();
                        } catch (InterruptedException e) {
                            // 该异常什么时候会抛出呢?就是其他线程调用interrupt方法
                            // 它也会让正在wait的线程被打断,打断之后,我们这边接到异常
                            // 就知道该线程被打断了,也可以对打断标记进行判断,于是可以继续进行处理。
                            // 当然了,更合理的方法是其他线程调用notify方法来唤醒
                            // 正在wait的线程
                            e.printStackTrace();
                        }
                    }
                    logger.log(Thread.currentThread().getName() + ":有烟没? " + hasCigarette);
                    if (hasCigarette) {
                        logger.log(Thread.currentThread().getName() + ":可以干活了: " + hasCigarette);
                    } else {
                        logger.log(Thread.currentThread().getName() + ":没干成活: " + hasCigarette);
                    }
                }
            }
        }, "小南").start();

        // 该线程与小南线程几乎一样,但是它等的是外卖而不是烟
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    logger.log(Thread.currentThread().getName() + ":外面送到没? " + hasTakeout);
                    while (!hasTakeout) {
                        logger.log(Thread.currentThread().getName() + ":没等待外卖先歇会: " + hasTakeout);
                        try {
                            room.wait();
                        } catch (InterruptedException e) {
                            // 该异常什么时候会抛出呢?就是其他线程调用interrupt方法
                            // 它也会让正在wait的线程被打断,打断之后,我们这边接到异常
                            // 就知道该线程被打断了,也可以对打断标记进行判断,于是可以继续进行处理。
                            // 当然了,更合理的方法是其他线程调用notify方法来唤醒
                            // 正在wait的线程
                            e.printStackTrace();
                        }
                    }
                    logger.log(Thread.currentThread().getName() + ":外面送到没? " + hasTakeout);
                    if (hasTakeout) {
                        logger.log(Thread.currentThread().getName() + ":可以干饭了: " + hasTakeout);
                    } else {
                        logger.log(Thread.currentThread().getName() + ":没干成饭: " + hasTakeout);
                    }
                }
            }
        }, "小女").start();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (room) {
                        logger.log(Thread.currentThread().getName() +":可以干活了 " + hasCigarette);
                    }
                }
            },"其他人").start();
        }
        Thread.sleep(1000);

        // 该送外卖的线程到底将谁叫醒了呢?
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (room) {
                    hasTakeout = true;
                    logger.log(Thread.currentThread().getName() +":外卖到了 " + hasTakeout);
                    // 这里要要注意,
                    // notify/notifyAll只能唤醒wait状态下的线程
                    // 对blocked状态下的线程毫无作用。
                    // 所以不管是wait还是notify/notifyAll,
                    // 他们都有一个前提就是线程都必须获得了对象锁。成为了owner之后才能调用这3个方法
                    // 所以这里要在synchronized代码块里调用notify,否则将抛出非法状态异常
                    //room.notify();
                    room.notifyAll();
                }
            }
        },"送外卖的").start();
    }
}

运行结果:
在这里插入图片描述
结果分析:
我们使用while去代替if,好处在于,判断有没有烟的逻辑是循环的,它第一次不满足条件,等到再次被唤醒的时候,root.wait执行下面的代码,然后下面没代码,它就又回到了while,去判断有没有烟,小南被喊醒,一看是送外卖的于是再次沉睡(送外卖的是把hasTakeout = true;设置为true,此时小南还是没有烟hasCigarette仍然为false),没有烟于是就再次wait,然后不会执行那句没有干成活的错误逻辑代码,因为我们是想让它干成活的。从而真正解决线程被虚假唤醒后做一些无用工。

wait/notify/notifyAll的正确使用姿势

通过以上5步我们总结出wait/notify/notifyAll的正确使用姿势:
当线程使用wait的时候,建议用while判断如果条件不成立继续等待,然后其他线程建议使用notifyAll去唤醒正待中的线程们。
在这里插入图片描述

多线程设计模式之一:保护性暂停

基本概念

定义:即Guarded Suspension,用在一个线程等待另一个线程的执行结果
定义说明了它要解决的问题,就是用在一个线程等待另一个线程的执行结果时使用。
保护性暂停的暂停就是当条件不满足的时候就去进行wait等待
要点

  1. 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个
    GuardedObject。
  2. ·如果有结果不断从一个线程到另一个线程那么此时就不能使用这个保护性暂停模式了,我们可以使用消息队列(见生产者/消费者)。
  3. JDK中,join的实现、Future的实现,采用的就是此模式。(还记不记得我们前几节用join一个线程等待另一个线程结束就可以拿到结果了,其实这也是我们保护性线程的一个应用)
  4. ·因为要等待另一方的结果,因此归类到同步模式。

比如线程2产生这个结果,然后线程1想要得到这个结果,那就可以让GuardedObject充当一个桥梁,让线程1、2都关联到这个对象上。
在这里插入图片描述
之所以称之为模式,那么就是有套路的,我们来代码实现一下。
大致思路,趁热打铁我们使用刚刚学习完的wait/notify/nogifyAll去让上图中的t1去等待GuardedObject中返回的结果,只要不返回就一直等待。t2呢它是负责产生结果的,将来产生的结果就会被t2赋值给response,赋值好了之后,它(t2)就唤醒在GuardedObject上等待的线程,然后t1就会被唤醒然后拿到值。以上就是我们整个模式实际使用时的一个大致分析。

代码实现

场景模拟:线程1等待线程2的下载结果。
GuardeObject 对象:

/**
 * @author diao 2021/4/21 23:17
 */


/**
 * 该类中就是我们上一节使用的wait/notifyAll正确使用方法
 */
public class GuardeObject {
    // 结果(因为我们这个结果可以是任意对象,所以这里设置为Object)
    private Object response;

    // 获取结果的方法
    public Object get() throws InterruptedException {
        // 这里为什么加while而不是直接wait呢?
        while (null == response) {
            // 这里为什么加锁呢?
            // 1、保护性暂停模式是两个线程对同一个线程进行操作的,
            // 所以多个线程之间要对同一个对象操作,那么要加锁
            // 2、wait、notify、notifyAll三个方法都必须是线程为资源锁的owner
            // 才能调用否则会报错误IllegalMonitorStateException
            synchronized (this) {
                this.wait();
            }
        }
        return response;
    }
    // 产生结果的方法
    public void complete(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            this.notifyAll();
        }
    }
}

下载类:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @author diao 2021/4/21 23:19
 */
public class DownLoader {
    public static List<String> download () throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL("https://www.baidu.com/").openConnection();
        BufferedReader bufferedReader =
                new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
        List<String> lines =new ArrayList<>();
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            lines.add(line);
        }
        return lines;
    }
}

测试类:

import java.io.IOException;
import java.util.List;

/**
 * @author diao 2021/4/21 23:25
 */
public class TestGuardeObject {
    // t1 等待 t2线程的下载结果
    public static void main(String[] args) {
        // 这里的使用同一个guardeObject对象
        // 原因1:保护性暂停模式,两个线程的使用同一个guardeObject对象
        // 原因2:代码设计到synchronized,所以多个线程要对同一个对象进行操作
        // 以确保共享成员变量的安全性(这里的共享成员变量是GuardeObject中的response对象)
        GuardeObject guardeObject = new GuardeObject();
        // 线程t1
        new Thread(new Runnable() {
            @Override
            public void run() {
                logger.log(Thread.currentThread().getName() + ":等待结果");
                try {
                    // 等待结果:至于等待多久得看线程2啥时候下载完
                    List<String> list = (List<String>)guardeObject.get();
                    logger.log(Thread.currentThread().getName() + ":结果大小" + list.size());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();

        // 线程t2
        new Thread(()->{
            logger.log(Thread.currentThread().getName() + ":执行下载");
            try {
                List<String> list = DownLoader.download();
                // 将结果传递给GuardeObject,并唤醒正在等待的线程
                guardeObject.complete(list);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

运行结果:
在这里插入图片描述
该模式的好处1:之前我们是使用join去等待一个线程结束然后另一个线程获取该线程的返回结果,但是现在我们的t2下载完了,不用结束,它还可以干其他的事情。
该模式的好处2:用join的那种方法,返回的变量只能设置成为全局的。现在返回的可以是局部的,两个线程中代码里返回的变量list。

代码增强1

增加一个超时的效果,比如我们上面的代码线程1在等待线程2下载完毕,但是比如我等待了2s后就不想等待了。显然上面暗中不带参数的guardeObject.get()等待方法是不行的,只要不下载完他就会一直等待。

修改guardeObject.get()方法添加超时检测,当线程1等待超时时就不再等待。

/**
 * @author diao 2021/4/21 23:17
 */


/**
 * 该类中就是我们上一节使用的wait/notifyAll正确使用方法
 */
public class GuardeObject {
    // 结果(因为我们这个结果可以是任意对象,所以这里设置为Object)
    private Object response;

    /**
     * 获取结果的方法
     * @param timeout  表示要等多久
     * @return
     * @throws InterruptedException
     */
    public Object get(long timeout) throws InterruptedException {
        synchronized (this) {
            // 开始时间
            long beginTime = System.currentTimeMillis();
            // 已经等待的时间
            long passedTime = 0;
            while (null == response) {
                // 这一轮循环应该等待的时间
                long waitTime = timeout - passedTime;
                // 等待的时间超过了最大等待时间,退出循环
                if (waitTime <= 0) {
                    break;
                }
                //synchronized (this) {
                // 这里为啥不用timeout?
                // 考虑到虚假唤醒的情况,
                // 例如当该线程在14:44:44被虚假唤醒了
                // 然后它就执行下面的代码再次进入while的时候,此时过去了1s,
                // 于是它应该再等待1s,而不是原来的2s
               // 避免虚假唤醒时再次等待的时间会变长,即被虚假唤醒时的耗时也算在本次wait的时间之内。
                this.wait(waitTime);  // 虚假唤醒
                //}
                // 已经等待的时间
                passedTime = System.currentTimeMillis() - beginTime;
            }
            return response;
        }
    }
    // 产生结果的方法
    public void complete(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            this.notifyAll();
        }
    }
}

测试代码:

import java.util.List;

/**
 * @author diao 2021/4/21 23:25
 */
public class TestGuardeObject2 {
    // t1 等待 t2线程的下载结果
    public static void main(String[] args) {
        // 这里的使用同一个guardeObject对象
        // 原因1:保护性暂停模式,两个线程的使用同一个guardeObject对象
        // 原因2:代码设计到synchronized,所以多个线程要对同一个对象进行操作
        // 以确保共享成员变量的安全性(这里的共享成员变量是GuardeObject中的response对象)
        GuardeObject guardeObject = new GuardeObject();
        // 线程t1
        new Thread(new Runnable() {
            @Override
            public void run() {
                logger.log(Thread.currentThread().getName() + ":等待结果");
                try {
                    // 等待结果:至于等待多久得看线程2啥时候下载完
                    Object response = guardeObject.get(2000);
                    logger.log(Thread.currentThread().getName() + ":结果是" + response);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();

        // 线程t2
        new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            logger.log(Thread.currentThread().getName() + ":执行下载");
            try {
                List<String> list = DownLoader.download();
                // 将结果传递给GuardeObject,并唤醒正在等待的线程
                guardeObject.complete(list);
                logger.log(Thread.currentThread().getName() + ":下载完毕");
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t2").start();
    }
}

打印结果:
在这里插入图片描述
可以看到当t1等待的时间超过设置的时间之后,t1就不会再等待了,它会退出去执行其他代码。
当等待时间没有超过设置时间的时候,t1会打印t2下载的结果。如将测试类中时间写为8000。
在这里插入图片描述
下面我们来测试一下虚假唤醒的情况,就是我们再线程2里直接返回guardeObject对象一个null,然后线程1调用guardeObject.get,但是此时返回为null,不满足退出wait的条件,于是它因该再等一会超过等待时间再推出,两端的等待时间加一块等于我们在线程1(guardeObject.get)里传入的时间才算正确。
测试方法:
在这里插入图片描述

在这里插入图片描述
打印结果
在这里插入图片描述

join原理

join方法的使用:比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
其实join的底层原理就是使用了上面的保护性暂停模式,来实现了我要等待另一个线程结束。当然它和保护性暂停模式还有区别。
保护性暂停是一个线程等待另一个线程的结果(返回结果后该线程仍然可以运行其他代码)。而join是一个线程等待另一个线程的结束
但是他们的方式都是差不多的。
我们来看一下join的源码,它是Thread的方法,
无参join实际上是调用了长整型参数的join

在这里插入图片描述
在这里插入图片描述
以上代码,
参数<0:如果传入的参数小于0,它会觉得是无效的抛出异常。
**参数=0:**如果传入的是0,它就一直等待,没有时限。isAlive代表这个线程是否还存活,如果该线程没有执行完代码,代表它就存活,反之代表线程已经执行完了代码。如果线程一直存活那么它就去wait(0)就是一直等待。所以传入参数为0,就一直等待。

在这里插入图片描述
**参数> 0:**如果传入参数>0,那么就是咱们上面说的保护性暂停里的添加超时增强的代码实现。保护性暂停的暂停就是当条件不满足的时候就去进行wait等待
在这里插入图片描述

代码增强2

我们继续改善我们的保护性暂停模式,让它具备更好的实用性。
我们思考以下场景:我们以面向对象的思想设计多个类,他们之间要通过多个线程进行通信。比如我设计一个居民类,他们使用多个线程去接受邮件(等待结果的线程)。
还有一个邮递员类,他们也是用多个线程去送邮件(产生结果的线程)。对应我们上面例子的等待结果的线程和产生结果的线程。

如下图所示:
图中Futures就好比居民楼一层的信箱(每个信箱有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员
如果需要在多个类之间使用GuardedObject对象(见上一小节,产生结果和接受结果都用的该对象),作为参数传递不是很方便,因此设计一个用来解耦的中间类,这样不仅能够解耦【结果等待者】和【结果生产者】,还能够同时支持多个任务的管理。Futrues里维护的一个集合,集合里有很多的GuardedObject对象。

在这里插入图片描述
以上代码待补全。。。。

最近诸多不顺:
总结了3句话
1、不放弃别人不愿放弃的,就得不到别人得不到的。
2、不要和不理解你的人解释你的行为,本身你们就不在同一个世界,没有必要。
3、愤怒的时候你需要做的第一件事就是:闭上自己的嘴巴。

线程通信的基础

park & unpark

基本使用
它们是LockSupport类中的方法

 		// 暂停当前线程,park之后线程对应的是wait状态
        LockSupport.park();
        // 恢复某个线程的运行
        LockSupport.unpark(暂停线程对象);

在这里插入图片描述
运行结果
在这里插入图片描述

与wait&notify不同点:unpark可以在park执行之前或者执行之后调用,也就是说,可以在先调用unpark然后再调用park,此时它也会唤醒线程,线程就可以继续往下执行了。
在这里插入图片描述
在这里插入图片描述

以上代码先unpark,然后在park,但是2s过后,程序仍然执行下去了。这是为什么呢?请看下面的原理部分。

特点

与Object的wait & notify相比

  1. wait, notify和notifyAll必须配合Object Monitor一起使用,而park,unpark不必
  2. park & unpark是以线程为单位来[阻塞]和[唤醒]线程(unpark的参数指定了唤醒的线程),而notify只能随机唤醒一个等待线程,
    notifyAll是唤醒所有等待线程,就不那么[精确]
  3. park & unpark可以先unpark,而wait & notify不能先notify
原理

原理之park & unpark
每个线程都有自己的-一个Parker对象(java层面是看不到得),由三部分组成_ counter, cond 和 _mutex 打个比喻

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

下面图文解释一下先调用park再调用unpark的情况
在这里插入图片描述
在这里插入图片描述
下面图文解释一下先调用unpark再调用park的情况
在这里插入图片描述
以后的很多原理会有用到park与unpark所以这里的知道它的原理。

线程状态转换

点击查看java线程中6种状态如何转换

我曾七次鄙视自己的灵魂: 第一次,当它本可进取时,却故作谦卑; 第二次,当它空虚时,用爱欲来填充; 第三次,在困难和容易之间,它选择了容易; 第四次,它犯了错,却借由别人也会犯错来宽慰自己; 第五次,它自由软弱,却把它认为是生命的坚韧; 第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副; 第七次,它侧身于生活的污泥中虽不甘心,却又畏首畏尾。 —卡里·纪伯伦撒旦

个人笔记,不喜勿喷

产品必备技能(十):如何绘制原型图?先要绘制页面流程图+页面原型图!附实操原型图、流程图实例(抖音) 文章目录目标题目实操步骤模板实操【抖音】APP的视频发布功能页面流程绘制页面流程图合格标准绘制页面原型图合格标准 目标 新入行的产品经理都对原型抱有向往的心境,但是有经验的产品经理不会一上来就画原型,而是先画页面流程再来画原型。通过学习,你将学会如何用页面流程图规划用户行为路径、如何借助原型识别问题、减少风险、有效沟通。 题目 任意选择一款你喜欢的产品,体验某个功能,下载并梳理此功能至少5个以上的页面流程跳转点(后面会解释),还原2 - 3个主要界面的低保真原型图。 实操步骤 可使用手绘以及Process 阅读详情

相关推荐

华为项目TR5评审后发现了重大缺陷的应急处理经验

华为TR5评审后发现重大缺陷时,启动分级应急机制:1)缺陷分级:按严重性(致命/严重/一般)评估影响;2)应急响应:跨部门会议制定方案,冻结生产并调配资源;3)根因修复:通过RCA定位问题,设计变更后回归测试;4)变更管理:评审修复影响,调整发布策略;5)持续改进:记录缺陷闭环,优化流程。案例显示,软件闪退问题通过热修复补丁和代码重构解决。关键工具包括Jira、FMEA等,确保缺陷在量产前拦截,降低风险。

从技术实践立足,从项目管理方法论延伸思考 1068

Juc19_从字节码角度看synchronize、Monitor类、monitorenter、monitorexit、深入理解同步方法

①. 从字节码角度分析synchronized实现。

qq_43614322的博客 316

快速上手CH340N电路设计(CH340N USB转串口模块 USB Type-C接口 CH340系列芯片讲解)

一、上模块 二、功能分析 l 芯片:CH340N l 输入接口:USB、TYPE-C l 输出接口:TTL(5V\3.3V\GND\TX\RX) l 指示灯:电源、TX、RX 三、CH340x芯片 l CH340 是一个 USB 总线的转接芯片,实现 USB 转串口 l 全速 USB 设备接口,兼容 USB V2.0 l 硬件全双工串口,内置收发缓冲区,支持通讯波特率 50bps~2Mbps l 支持常用的 MODEM 联络信号 RTS、DTR、DCD、RI、DS...

东小东博客 5万+

Java 对象头、监视器、synchronized

对象头 HotSpot虚拟机的对象头包括两部分信息: MarkWord 第一部分MarkWord,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“MarkWord”。 Class类型指针 对象头的另外一部分是Class类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例. ...

haoranhaoshi的博客 880

Pcileech(DMA)开源项目简述

希望大DMA家能正确认识FPGA,正确使用DMA技术,对于市场上各类伪装成花里胡哨设备的FPGA板卡它的本质是不会变的,不要上当受骗。

2301_79773847的博客 5959

【牛股启动线】副图指标使用技术说明,图文案例分享,通达信炒股指标

如上图,在紫色线在黑色虚线上时,在出现紫色柱线时,通常会出现强势拉升的行情​。柱线加长加上紫色线上扬,进场跟随做多抓强势行情,紫色柱线和紫色线同时做多持续则可以继续持股。如上图,副图指标【牛股启动线】,指标信号包括紫色线走势信号和紫色柱线信号,以及底部的四条虚线技术信号​,还有一条绿色的参考线,一条黑色的虚线两天分档线。如上图,参考买点,紫色柱线开始出现时进场买点,在上图标记的位置对应的就是进场买点​。紫色柱线加长+紫色线上扬,多头共振持股,行情继续拉升阶段,持股待涨或顺势​进场做多。

weixin_69760830的博客 1906

java多线程——Monitor机制和synchronized关键字

synchronized的用法 指定加锁对象:对给定对象加锁,进入同步代码前需要活的给定对象的锁。 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类(当前类内部的class对象)的锁。 内部原理 Synchronized在古老的年代被成为重量级锁。但是java1.6对其进行了优化。为了减少...

define_us的专栏 700

C语言图形化界面——含图形、按钮、鼠标、进度条等部件制作(带详细代码、讲解及注释)

本文借助easyx库函数(提供下载链接),很方便地用C语言实现了鼠标、按钮、进度条、坐标尺等操作,为普通C程序的友好人机交互界面提供了一种简单便捷的方案。

体会编程语言独到的美 17万+

Java之synchronized的JVM底层实现原理精简理解

Java之synchronized的JVM底层实现原理精简理解

weixin_44519342的博客 532

多核系统核间通信(IPC)设计:从硬件机制到软件实现

在多核处理器与嵌入式系统设计中,核间通信(IPC)是实现计算资源协同工作的核心技术。其原理基于共享内存、硬件邮箱与中断等底层硬件机制,通过构建结构化的消息传递通道,实现核间高效、可靠的数据交换。该技术的核心价值在于打破多核间的壁垒,将分散的计算单元整合为有机整体,从而显著提升系统整体性能与响应能力。在工程实践中,IPC广泛应用于智能座舱、边缘AI网关、实时控制系统等场景,用于处理如仪表显示、AI推理、实时控制等复杂任务。本文聚焦于基于共享内存与VRING的消息队列实现,深入剖析缓存一致性、内存屏障等关键问题

weixin_27006759的博客 591

使用GitLab的VS Code插件 GitLab Workflow 来进行项目的DevOps管理,助力研发效能

使用VS Code的GitLab Workflow插件,进行项目的DevOps管理,将效率提升到MAX。直接在VScode中管理流水线,一键跳转到创建 Issues,MR

知无涯 5290

Monitor监视器锁

可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。也就是通常说Synchronized的对象锁,MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。在Java虚拟机(HotSpot)中,

qq_33240556的博客 694

Cesium教程(十七):淹没分析

淹没分析是根据某片区域的地形及洪水流量速度,动态模拟该地形区域水位逐渐上涨的淹没过程。该功能可适用于山区、丘陵等地形起伏较大区域,模拟洪水涨到安全限定高度的淹没过程,为防洪水救灾提供一定的参考。此外,还可以为河谷地带修建水利工程设计与选址提供参考依据。

WwLK123的博客 3006

Java线程中的sleep和wati的区别以及join、yield方法

1.sleep来自Thread类,而wait来自Object类; 2.sleep方法没有释放锁,而wait方法释放了锁; 3.wait,notify,notifyAll只能在同步方法或者同步代码块使用,而sleep可以在任何地方使用; 4.wait可以指定时间也可以不指定时间,而sleep必须指定时间; 5.sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。 ...

weixin_44997483的博客 661

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

JAVA 线程Wati使用中出现的问题

首先先声明一点,wiat()方法必须释放CPU资源和锁资源,因此wiat()方法必须在锁里面调用,不在锁里面写就没有锁可释放,没锁资源可释放的话就会抛出异常。有了上述的铺垫我们来看以下代码: package test; /* 主线程测试类 */ public class Thread3 extends Thread{ public static void main(String[] args) throws InterruptedException { //创建对象并且

test253506088的博客 1216

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

synchronized关键字及wait()/notify()/notifyAll()详解

概念 JDK官网中对synchronized关键字有如下定义,synchronized关键字可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多线程是可见的,那么对该对象的所有读或者写都将通过同步的方式来进行。具体表现如下: synchronized关键字提供了一种锁机制,能够确保共享变量的互斥访问,从而防止数据不一致问题的出现。 synchronized关键字包括monitor enter和monitor exit两个JVM指令,它能够保证在任何时候任何线程执行到monitor

Lizhj的博客 1973

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Java并发编程之synchronized关键字与Monitor机制

调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。 【转载】Java多线程(二)——Java对象的Monitor机制 synchronized、monitor、wait、notify ...

yzpbright的博客 527

synchronized详解,同步锁互斥锁自旋锁分析。MonitorJVM底层实现原理

目录状态对象synchronized修饰代码块synchronized修饰方法synchronized修饰静态方法Monitor设计的概念互斥与同步定义synchronized底层原理用户态和内核态资源调度存在问题解决方案互斥锁属性MonitorMonitor对象是啥? 状态对象 如果一个对象有被修改的成员变量 被称为有状态的对象相反如果没有可被修改的成员变量 称为无状态的对象 示例 public class MyThreadTest { public static void main(Stri

qq_42261668的博客 967

java monitorenter_java并发:Synchronized 原理

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify...

weixin_29149341的博客 227

15-synchronized保证线程安全的原理(jvm层面)

synchronized用于代码块 代码: public int xx () { // monitorenter synchronized (Sequence.class) { if(value &gt; 0) { return value; } else { return -1; } } // monitorexit ...

cckkpp的博客 1078

Java并发编程之 synchronized

Java中的每一个对象都可以作为锁。具体表现为以下3种形式: 对于普通同步方法,锁是当前实例对象 对于静态同步方法,锁是当前类的Class对象 对于同步代码块,锁是synchonized括号里配置的对象 都对于同步代码块而言,JVM是使用monitorenter和monitorexit实现的: monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象

qq_43613793的博客 1179

Java多线程wait/notify原理

1. 引入wait/notify 回顾:在之前的学习中,当我们创建一个对象后,synchronized给对象上锁,JVM会给对象头关联一个Monitor对象,这个Monitor由三部分组成。 一是Owner对象,里面存储的是创建该对象的线程 二是EntryList,想试图获取该对象资源的其它堵塞线程队列 三是WaitSet,存储的是放弃对象锁的线程 Owner线程中的锁对象,如果发现条件不满足,调用wait()方法,既可以进入到WaitSet变为WAITING状态 EntryList下和WaitSet下

LIUZEYU12A 1158

synchronized在jvm中的实现原理、在字节码层面上的实现同步的原理

synchtonized

qq_41174684的博客 2778

《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile

《JUC并发编程 - 原理篇》Monitor | synchronized | wait&notify | join | park&unpark | 指令级并行 | volatile

LXYDSF的博客 535

多线程——多线程安全(synchronized和volatile)、wait和notify

假设 count = 0,使用线程一和线程二同时对 count++,预期结果是 count = 2,但是如果是上述情况的话,线程一拿走 0 到他的寄存器进行计算得 1,还没将 1 返回到内存中,线程二就像 内存 中 count 的值 0 拿到了他的寄存器中,然后再进行计算得 1,最后两个线程将其寄存器的值返回到内存中 的 count,结果是 1。当一个线程还没有释放锁,然后又尝试获取锁时,会出现如下情况:第一次获取锁成功,第二次获取锁时,锁还没有被释放,则该线程就会一直处于阻塞状态,直到锁被释放。

AwwzJ_0821的博客 1247

Synchronized 用法和底层原理

一、对象锁和类锁 1. 对象锁 在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。 2. 类锁 在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所...

数据与算法架构提升之路专栏 2802
上一篇: logstash7.0.1将数据从mysql5.7同步至es7.0.1大数据量下同步慢的问题
下一篇: 并发编程之六:ReentrantLock线程活跃性问题(死锁、活锁、饥饿),线程执行顺序
java_xxxx
博客等级 码龄9年 127粉丝 99原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值