精华内容
下载资源
问答
  • 2019-04-16 14:46:29

    java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。并且锁只能升级不能降级。

    在讲这三个锁之前,我先给大家讲清楚自旋和对象头的概念。

    自旋
    现在假设有这么一个场景:有两个线程A,B在竞争一个锁,假设A拿到了,这个时候B被挂起阻塞,一直等待A释放了锁B才得到使用权。在操作系统中阻塞和唤醒是一个耗时操作,如果A在很短的时间内就释放了锁,当这个时间与阻塞唤醒比较起来更短的时候,我们将B挂起,其实不是一个最优的选择。 
    自旋是指某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取锁。虽然CPU的时间被消耗了,但是比线程下文切换时间要少。这个时候使用自旋是划算的。 
    如果是单核处理器,一般建议不要使用自旋锁。因为只有单个处理器,自旋占用的时间片使得代价很高。 
    而偏向锁、轻量锁、重量锁也是一个锁围绕着如何使得程序运行的更加“划算”而进行改变的。

    对象头
    HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

    HotSpot虚拟机的对象头(Object Header)包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。 
    在32位的HotSpot虚拟机 中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志 位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示。 


    偏向锁
    引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令。 
    当只有一个线程去竞争锁的时候,我们不需要阻塞,也不需要自旋,因为只有一个线程在竞争,我们只要去判断该偏向锁中的ThreadID是否为当前线程即可。如果是就执行同步代码,不是就尝试使用CAS修改ThreadID,修改成功执行同步代码,不成功就将偏向锁升级成轻量锁。

    轻量锁
    获取轻量锁的过程与偏向锁不同,竞争锁的线程首先需要拷贝对象头中的Mark Word到帧栈的锁记录中。拷贝成功后使用CAS操作尝试将对象的Mark Word更新为指向当前线程的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁。如果更新失败,那么意味着有多个线程在竞争。 
    当竞争线程尝试占用轻量级锁失败多次之后(使用自旋)轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。

    重量锁
    重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。

     

    基础知识之一:锁的类型
    锁从宏观上分类,分为悲观锁与乐观锁。

    乐观锁
    乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。

    java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

    悲观锁
    悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。java中的悲观锁就是Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。

    基础知识之二:java线程阻塞的代价
    java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

    如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间;
    如果对于那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然非常糟糕的。
    synchronized会导致争用不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁,为了缓解上述性能问题,JVM从1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。

    明确java线程切换的代价,是理解java中各种锁的优缺点的基础之一。

    重量级锁Synchronized

    è¿éåå¾çæè¿°

    在JDK1.5之前都是使用synchronized关键字保证同步的,Synchronized的作用相信大家都已经非常熟悉了;

    它可以把任意一个非NULL的对象当作锁。

    作用于方法时,锁住的是对象的实例(this);
    当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程;
    synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。
    Synchronized的实现
    实现如下图所示;

    它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

    Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;

    Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;

    Wait Set:哪些调用wait方法被阻塞的线程被放置在这里;

    OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;

    Owner:当前已经获取到所资源的线程被称为Owner;

    !Owner:当前释放锁的线程。

    JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。

    OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。

    处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。

    Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。

    Synchronized的底层原理

    对于普通同步方法,锁是当前实例对象。
    对于静态同步方法,锁是当前类的Class对象。
    对于同步方法块,锁是Synchonized括号里配置的对象。

    实现的原理:

    synchronized是基于Monitor来实现同步的。
    Monitor从两个方面来支持线程之间的同步:互斥执行、协作

    • 1、Java 使用对象锁 ( 使用 synchronized 获得对象锁 ) 保证工作在共享的数据集上的线程互斥执行。
    • 2、使用 notify/notifyAll/wait 方法来协同不同线程之间的工作。
    • 3、Class和Object都关联了一个Monitor。

    Monitor 的工作机理

    • 1:线程进入同步方法中。
    • 2:为了继续执行临界区代码,线程必须获取 Monitor 锁。如果获取锁成功,将成为该监视者对象的拥有者。任一时刻内,监视者对象只属于一个活动线程(The Owner)
    • 3:拥有监视者对象的线程可以调用 wait() 进入等待集合(Wait Set),同时释放监视锁,进入等待状态。
    • 4:其他线程调用 notify() / notifyAll() 接口唤醒等待集合中的线程,这些等待的线程需要重新获取监视锁后才能执行 wait() 之后的代码。
    • 5:同步方法执行完毕了,线程退出临界区,并释放监视锁

    synchronized的锁优化

    • 锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。其中主要就是偏向锁,轻量级锁,重量级锁。


    参考:https://blog.csdn.net/Cs_hnu_scw/article/details/79635874 
    参考:https://blog.csdn.net/zqz_zqz/article/details/70233767 
    参考:https://blog.csdn.net/makecontral/article/details/79435933 

    更多相关内容
  • 升级过程(偏向/量级/重量级

    千次阅读 多人点赞 2021-04-09 13:00:09
    每次去拿数据的时候都认为别人不会修改,所以不会上,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样更新),如果...

    锁的前置知识

    如果想要透彻的理解java锁的来龙去脉,需要先了解锁的基础知识:锁的类型、java线程阻塞的代价、Markword。

    锁的类型

    锁从宏观上分类,分为悲观锁与乐观锁。

    乐观锁

    乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低。每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。

    java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

    悲观锁

    悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高。每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会阻塞(block)直到拿到锁。

    java中的悲观锁就是Synchronize、AQS框架下的锁则是先尝试CAS乐观锁去获取锁,获取不到才会转换为悲观锁,如:ReentrantLock。

    线程阻塞的代价

    java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与核心态之间切换,这种切换会消耗大量的系统资源。因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

    1. 如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间;
    2. 如果对于那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然非常糟糕的。

    synchronized会导致争用不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁。为了缓解上述性能问题【synchronized】,JVM从1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。

    明确java线程切换的代价,是理解java中各种锁的优缺点的基础之一。

    Mark Word

    在介绍java锁之前,先说下什么是markword,markword是java对象数据结构中的一部分,要详细了解java对象的结构可以点击这里,这里只做markword的详细介绍,因为对象的markword和java各种类型的锁密切相关;

    markword数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,它的最后2bit是锁状态标志位,用来标记当前对象的状态,对象的所处的状态,决定了markword存储的内容,如下表所示:

    状态标志位存储内容
    未锁定01对象哈希码、对象分代年龄
    轻量级锁定00指向锁记录的指针
    膨胀(重量级锁定)10执行重量级锁定的指针
    GC标记11空(不需要记录信息)
    可偏向01偏向线程ID、偏向时间戳、对象分代年龄

    32位虚拟机在不同状态下markword结构如下图所示:

    这里写图片描述

    了解了markword结构,有助于后面了解java锁的加锁解锁过程;

    前面提到了java的4种锁,他们分别是重量级锁、自旋锁、轻量级锁和偏向锁。不同的锁有不同特点,每种锁只有在其特定的场景下,才会有出色的表现,java中没有哪种锁能够在所有情况下都能有出色的效率,引入这么多锁的原因就是为了应对不同的情况;

    Java中的锁

    偏向锁

    Java偏向锁(Biased Locking)是Java6引入的一项多线程优化,它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。

    偏向锁,顾名思义它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。
    如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

    偏向锁获取过程

    1. 访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01,确认为可偏向状态。
    2. 如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤5,否则进入步骤3。
    3. 如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行5;如果竞争失败,执行4。
    4. 如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。(撤销偏向锁的时候会导致stop the world)
    5. 执行同步代码。

    注意:第四步中到达安全点safepoint会导致stop the world,时间很短。

    偏向锁获取过程

    ​ 偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

    偏向锁的适用场景

    ​ 始终只有一个线程在执行同步块,在它没有执行完释放锁之前,没有其它线程去执行同步块,在锁无竞争的情况下使用,一旦有了竞争就升级为轻量级锁,升级为轻量级锁的时候需要撤销偏向锁,撤销偏向锁的时候会导致stop the world操作;
    在有锁的竞争时,偏向锁会多做很多额外操作,尤其是撤销偏向锁的时候会导致进入安全点,安全点会导致stw,导致性能下降,这种情况下应当禁用。

    查看停顿–安全点停顿日志

    要查看安全点停顿,可以打开安全点日志,通过设置JVM参数 -XX:+PrintGCApplicationStoppedTime 会打出系统停止的时间,添加 -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 这两个参数会打印出详细信息,可以查看到使用偏向锁导致的停顿,时间非常短暂,但是争用严重的情况下,停顿次数也会非常多;

    注意:安全点日志不能一直打开,只在问题排查时打开:

    1. 安全点日志默认输出到stdout,一是stdout日志的整洁性,二是stdout所重定向的文件如果不在/dev/shm,可能被锁。
    2. 对于一些很短的停顿,比如取消偏向锁,打印的消耗比停顿本身还大。
    3. 安全点日志是在安全点内打印的,本身加大了安全点的停顿时间。

    如果在生产系统上要打开,再再增加下面四个参数:-XX:+UnlockDiagnosticVMOptions -XX: -DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log 打开Diagnostic(只是开放了更多的flag可选,不会主动激活某个flag),关掉输出VM日志到stdout,输出到独立文件,/dev/shm目录(内存文件系统)。

    这里写图片描述
    第一部分是时间戳,VM Operation的类型;

    第二部分是线程概况,被中括号括起来【total: 安全点里的总线程数、initially_running: 安全点开始时正在运行状态的线程数、wait_to_block: 在VM Operation开始前需要等待其暂停的线程数】

    第三部分是到达安全点时的各个阶段以及执行操作所花的时间【spin:等待线程响应safepoint号召的时间、block:暂停所有线程所用的时间、sync:等于spin+block,这是从开始到进入安全点所耗的时间,可用于判断进入安全点耗时、cleanup:清理所用时间、vmop:真正执行VMOperation的时间】其中最重要的是vmop。

    可见,那些很多但又很短的安全点,全都是RevokeBias, 高并发的应用会禁用掉偏向锁。

    Jvm开启/关闭偏向锁

    • 开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
    • 关闭偏向锁:-XX:-UseBiasedLocking

    轻量级锁

    轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;

    轻量级锁加锁过程

    1. 在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图所示:

      这里写图片描述
    2. 拷贝对象头中的Mark Word复制到锁记录中;

    3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。

    4. 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图所示:

      这里写图片描述
    5. 如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

    轻量级锁释放过程

    释放锁线程视角:由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。

    因为重量级锁被修改了,所有display mark word和原来的markword不一样了。怎么补救?就是进入mutex前,compare一下obj的markword状态,确认该markword是否被其他线程持有。此时如果线程已经释放了markword,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。

    尝试获取锁线程视角:如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改markword,修改重量级锁,表示该进入重量锁了。

    还有一个注意点:等待轻量锁的线程不会阻塞,它会一直自旋等待锁,并如上所说修改markword。

    这就是自旋锁,尝试获取锁的线程,在没有获得锁的时候,不被挂起,而转而去执行一个空循环,即自旋。在若干个自旋后,如果还没有获得锁,则才被挂起,获得锁,则执行代码。

    自旋锁

    自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

    但是线程自旋是需要消耗CPU的,说白了就是让CPU在做无用功,如果一直获取不到锁,那线程也不能一直占用CPU自旋做无用功,所以需要设定一个自旋等待的最大时间。如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

    自旋锁的优缺点

    :自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!

    :但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu做无用功,占着茅坑又不拉屎,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要CPU的线程又不能获取到cpu,造成cpu的浪费。所以这种情况下我们要关闭自旋锁;

    自旋锁时间阈值

    ​ 自旋锁的目的是为了占着CPU的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用CPU资源,进而会影响整体系统的性能。因此自旋的周期选的额外重要!

    ​ JVM对于自旋周期的选择,jdk1.5这个限度是一定的写死的,在1.6引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时JVM还针对当前CPU的负荷情况做了较多的优化:

    1. 如果平均负载小于CPUs则一直自旋;
    2. 如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞;
    3. 如果正在自旋的线程发现Owner发生了变化则延迟自旋时间(自旋计数)或进入阻塞;
    4. 如果CPU处于节电模式则停止自旋;
    5. 自旋时间的最坏情况是CPU的存储延迟(CPU A存储了一个数据,到CPU B得知这个数据直接的时间差);
    6. 自旋时会适当放弃线程优先级之间的差异;

    自旋锁的开启

    JDK1.6中 -XX:+UseSpinning 开启;-XX:PreBlockSpin=10 为自旋次数;
    JDK1.7后,去掉此参数,由jvm控制;

    重量级锁Synchronized

    在JDK1.5之前都是使用synchronized关键字保证同步的,它可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。

    • 普通同步方法,锁是当前实例对象 ;
    • 静态同步方法,锁是当前类的class对象 ;
    • 同步方法块,锁是括号里面的对象;

    Synchronized的实现

    这里写图片描述

    它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

    1. Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
    2. Entry List:候选者队列 ,Contention List中那些有资格成为候选资源的线程被移动到Entry List中;
    3. Wait Set:阻塞队列,哪些调用wait方法被阻塞的线程被放置在这里;
    4. OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;
    5. Owner:当前已经获取到所资源的线程被称为Owner;
    6. !Owner:当前释放锁的线程。

    JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。

    OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。

    处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。

    Synchronized是非公平锁,在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源。

    Synchronized锁的演变过程

    1. 检测Mark Word里面是不是当前线程的ID,如果是则表示当前线程处于偏向锁;
    2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1;
    3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁;
    4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁;
    5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁;
    6. 如果自旋成功则依然处于轻量级状态;
    7. 如果自旋失败,则升级为重量级锁;

    在所有的锁都启用的情况下线程进入临界区时会先去获取偏向锁,如果已经存在偏向锁了,则会尝试获取轻量级锁,启用自旋锁,如果自旋也没有获取到锁,则使用重量级锁,没有获取到锁的线程阻塞挂起,直到持有锁的线程执行完同步块唤醒他们;

    偏向锁是在无锁争用的情况下使用的,也就是同步开在当前线程没有执行完之前,没有其它线程会执行该同步块,一旦有了第二个线程的争用,偏向锁就会升级为轻量级锁,如果轻量级锁自旋到达阈值后,没有获取到锁,就会升级为重量级锁;

    注意:如果线程争用激烈,那么应该禁用偏向锁。

    关注公众号 ,专注于java大数据领域离线、实时技术干货定期分享!个人网站 www.lllpan.top

    在这里插入图片描述

    展开全文
  • Synchronized原理(量级篇)

    千次阅读 2021-09-26 18:15:24
    量级是JDK1.6之中加入的新型机制,它名字中的“量级”是相对于使用操作系统互斥量来实现的传统而言的,因此传统的机制就称为“重量级”。首先需要强调一点的是,量级并不是用来代替重量级的,它...

    Synchronized原理(轻量级锁篇)

    简述

    介绍

    轻量级锁是JDK1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

    轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

    引入轻量级锁的目的

    JVM的开发者发现在很多情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。

    优点

    竞争的线程不会阻塞,使用自选,提高程序响应速度。

    缺点

    如果一直不能获取到锁,长时间的自旋会造成CPU消耗。

    适用场景

    适用于少量线程竞争锁对象,且线程持有锁的时间不长,追求响应速度的场景。


    工作流程

    在这里插入图片描述

    入口

    轻量级锁的进入方式有三种

    • 对象处于未锁定不可偏状态

      在这里插入图片描述

      此状态下对象不能进入偏向锁模式,当有线程尝试获取锁时,会通过轻量级锁的方式获取锁。

    • 对象锁已经偏向于线程(不考虑重偏向场景)

      在这里插入图片描述

      当锁已经偏向于线程,且线程处于锁定状态或处于未锁定但不允许重偏向的情况下,其它的线程尝试获取锁时,会触发偏向锁撤销,然后升级为轻量级或重量级锁定。

    • 对象被轻量级锁定

      在这里插入图片描述

      当对象已经被轻量级锁定的时候,会判断是否是锁重入,如果是重入的话,会记录一条Displaced Mark Word为空的Lock Record。如果不是重入,会膨胀为重量级锁。需要注意的是,即使膨胀为重量级锁,没有获取到锁的线程也不会马上阻塞,而是通过适应性自旋尝试获取锁,当自旋次数达到临界值后,才会阻塞未获取到的线程。JVM认为获取到锁的线程大概率会很快的释放锁,这样做是为了尽可能的避免用户态到内核态的切换。


    加锁过程

    code 1 :判断对象是否是无锁状态(低三位 = 001),如果是,执行code 2,如果不是,执行code 4

    code 2:在栈中建立一个Lock Record,将无锁状态的Mark Word拷贝到锁记录的Displaced Mark Word中,将owner指向当前对象。

    code 3:尝试通过CAS 将锁对象的 Mark Word 更新为指向Lock Record的指针,如果更新成功,该线程获取到轻量级锁,并且需要把对象头的Mark Word的低两位改成10(注意这里修改的是对象头的Mark WordLock Record中记录的还是无锁状态的Mark Word);如果更新失败,执行code 4

    code 4:对象是轻量级锁定状态,判断对象头的 Mark Word是否指向当前线程的栈帧。如果是,则这次为锁重入,将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录重入,该线程重入轻量级锁。如果不是,执行code 5

    code 5:线程获取轻量级锁失败,锁膨胀为重量级锁,对象头的Mark Word改为指向重量级锁monitor的指针。获取失败的线程不会立即阻塞,先适应性自旋,尝试获取锁。到达临界值后,阻塞该线程,直到被唤醒。

    适应性自旋

    自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

    图解

    无锁状态获取锁

    线程执行到同步块时,同步对象处于无锁状态,锁标志位为01,偏向标志位为0,偏向锁被禁用,对象处于无锁态。

    在这里插入图片描述

    在加锁前,虚拟机需要在当前线程的栈帧中建立锁记录(Lock Record)的空间。Lock Record 中包含一个 _displaced_header 属性,用于存储锁对象的 Mark Word 的拷贝。

    在这里插入图片描述

    将锁对象的 Mark Word 复制到锁记录中,这个复制过来的记录叫做 Displaced Mark Word。具体来讲,是将 mark word 放到锁记录的 _displaced_header 属性中,将Owner指向当前对象。

    在这里插入图片描述

    虚拟机使用 CAS 操作尝试将锁对象的 Mark Word 更新为指向锁记录的指针。如果更新成功,这个线程就获得了该对象的锁。

    在这里插入图片描述

    更新成功后,需要修改原对象头Mark Word中的锁状态标志位为00,目的是告诉其它线程此对象已经被轻量级锁定。

    在这里插入图片描述

    重入锁

    当对象处于加锁状态时,会去检验Mark Word是否指向当前线程的栈帧,如果是则将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录线程重入锁。

    在这里插入图片描述

    非重入锁

    如果指向的不是当前线程的栈帧则会触发锁膨胀,膨胀为重量级锁。

    在这里插入图片描述
    Thread2把锁膨胀为重量级锁,Thread1获取到重量级锁,Thread2适应性自旋尝试获取锁,到达临界值后进入等待队列阻塞。
    在这里插入图片描述
    这里注意执行锁膨胀的线程为Thread2,而非原轻量级锁的持有者Thread1


    解锁过程

    轻量级锁加锁时有锁重入的可能,同样的,在解锁时也需要判断是否是锁重入解锁。

    code 1 :检索当前线程栈中的锁记录空间,从低位往高位找到第一条和此对象有关的Lock Record。加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null,相应的,在解锁时需要判断Displaced Mark Word是否为 null,如果是,则说明是锁重入解锁,移除onwer的指向,不做替换操作;如果不是,执行code 2

    code 2:通过CAS把当前线程栈帧Lock Record中的Displaced Mark Word替换到对象头的Mark Word中去,如果替换成功,则轻量级解锁成功;如果替换失败,则说明发生了锁膨胀,对象现在是重量级锁定状态,执行code 3

    code 3:执行重量级锁释放流程(详细过程下篇文章会将),释放重量级锁,同时唤醒被阻塞的线程去获取锁。

    图解

    非重入释放锁

    解锁的思路是使用 CAS 操作把当前线程的栈帧中的 Displaced Mark Word 替换回锁对象中去,如果替换成功,则解锁成功。

    在这里插入图片描述

    CAS成功

    在这里插入图片描述

    CAS失败

    在这里插入图片描述

    轻量级锁未释放前被其它线程尝试获取,此时Mark Word指针已经被替换为指向Monitor,释放锁时CAS会失败,此时需要走重量级解锁流程。

    PS:关于重量级解锁流程下篇文章会补充。

    重入释放锁

    加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null。相对应地,解锁时,如果判断 Displaced Mark Word 为 null 则说明是锁重入,不做替换操作。

    在这里插入图片描述


    归纳总结

    轻量级锁的适用场景

    少量的线程竞争锁,且所有者线程占用锁的事件补偿,追求响应速度的场景。

    什么时候会升级为轻量级锁

    当对象的偏向模式被关闭、对象处于已偏向已锁定、已偏向未锁定但不支持重偏向的场景下,就会升级为轻量级锁。

    什么时候会升级为重量级锁

    当竞争产生时就会升级为重量级锁,比如,两个线程同时获取锁,成功的线程会获取到轻量级锁,失败的线程会执行锁膨胀,升级为重量级锁。

    轻量级锁怎样实现锁重入

    当轻量级锁已经被线程持有,且对象头的Mark Word指向的是当前线程的栈帧时,会把本条Lock RecordDisplaced Mark Word 设置为 null,实现锁重入。当重入解锁时,只需要修改所有者onwer的指向。

    轻量级锁是否会自旋

    轻量级锁流程不会自旋,自旋发生在产生竞争后,获取失败的线程将锁膨胀为重量级锁。失败的线程不会立刻阻塞,而是先尝试适应性自旋,等待所有者释放锁,当到达临界值后再阻塞。

    End

    轻量级锁可以看作是偏向锁重偏向的升级版,加入了有锁无锁的状态转换,即使当竞争产生时升级到重量级锁,也不会马上阻塞线程,而是通过适应性自旋来决定是否阻塞,提高了性能。轻量级锁相较于偏向锁来说,简单一些,下一篇会着重介绍重量级锁,以及自旋的线程是怎样进入重量级锁的等待队列的。

    参考文献

    jdk源码剖析二: 对象内存布局、synchronized终极原理 - 只会一点java - 博客园 (cnblogs.com)

    死磕Synchronized底层实现–轻量级锁 · Issue #14 · farmerjohngit/myblog (github.com)

    展开全文
  • 文章目录一、简介二、Java对象头中的Mark Word三、偏向四、量级五、重量级六、自旋七、升级过程 一、简介 在讲解这些概念之前,我们要明确的是这些不等同于Java API中的ReentratLock这种,这些...

    一、简介

    在讲解这些锁概念之前,我们要明确的是这些锁不等同于Java API中的ReentratLock这种锁,这些锁是概念上的,是JDK1.6中为了对synchronized同步关键字进行优化而产生的的锁机制。这些锁的启动和关闭策略可以通过设定JVM启动参数来设置,当然在一般情况下,使用JVM默认的策略就可以了。

    二、Java对象头中的Mark Word

    HotSpot中,Java的对象内存模型分为三部分,分别为对象头、实例数据和对齐填充。而对象头中分为两部分,一部分是“Mark Word”(存储对象自身的运行时数据,32bit或64bit,可复用);另一部分是指向它的类的元数据指针。

    因为synchronized是Java对象的内置锁,所以其优化策略(即偏向锁等)的信息都包含在Mark Word中,让我们先看一下Mark Word的结构。

    在这里插入图片描述

    在这里插入图片描述
    其中最重要的是“锁标志位”和“是否偏向锁”,锁标志位代表了当前对象内置锁的状态,不同的锁状态下Mark Word存储的信息是不同的,因此称为可复用。

    三、偏向锁

    通俗的讲,偏向锁就是在运行过程中,对象的锁偏向某个线程。即在开启偏向锁机制的情况下,某个线程获得锁,当该线程下次再想要获得锁时,不需要再获得锁(即忽略synchronized关键词),直接就可以执行同步代码,比较适合竞争较少的情况。
    偏向锁的获取流程:

    (1)查看Mark Word中偏向锁的标识以及锁标志位,若是否偏向锁为1且锁标志位为01,则该锁为可偏向状态。

    (2)若为可偏向状态,则测试Mark Word中的线程ID是否与当前线程相同,若相同,则直接执行同步代码,否则进入下一步。

    (3)当前线程通过CAS操作竞争锁,若竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行同步代码,若竞争失败,进入下一步。

    (4)当前线程通过CAS竞争锁失败的情况下,说明有竞争。当到达全局安全点时之前获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

    偏向锁的释放流程:

    偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁状态的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点(即没有字节码正在执行),它会暂停拥有偏向锁的线程,撤销后偏向锁恢复到未锁定状态或轻量级锁状态。

    下面看一个实验:

    /** *  * 开启偏向锁参数:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0  耗时4000ms左右 * 警用偏向锁参数:-XX:-UseBiasedLocking  耗时1900ms左右 */public class VectorTest {
        public static void main(String[] args) {     
           long time1 = System.currentTimeMillis();       
            Vector<Integer> vector = new Vector<Integer>();    
                for (int i = 0; i < 100000000; i++){       
                     vector.add(100);//add是synchronized操作        }       
                      System.out.println(System.currentTimeMillis() - time1);    }}
    

    因为Vector是同步容器,其add操作是有synchronized修饰的,在JVM参数中设置开启偏向锁或禁用偏向锁时,其耗时相差很大,说明偏向锁对synchronized关键词的优化在单个线程中或竞争较少的线程中是很成功的。但是在多线程竞争十分频繁的情况下,偏向锁不仅不能提高效率,反而会因为不断地重新设置偏向线程ID等其他消耗而降低效率。

    四、轻量级锁

    轻量级锁不是用来替代传统的重量级锁的,而是在没有多线程竞争的情况下,使用轻量级锁能够减少性能消耗,但是当多个线程同时竞争锁时,轻量级锁会膨胀为重量级锁。

    轻量级锁的加锁过程:

    (1)当线程执行代码进入同步块时,若Mark Word为无锁状态,虚拟机先在当前线程的栈帧中建立一个名为Lock Record的空间,用于存储当前对象的Mark Word的拷贝,官方称之为“Dispalced Mark Word”,此时状态如下图:

    在这里插入图片描述
    (2)复制对象头中的Mark Word到锁记录中。

    (3)复制成功后,虚拟机将用CAS操作将对象的Mark Word更新为执行Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。如果更新成功,则执行4,否则执行5。;

    (4)如果更新成功,则这个线程拥有了这个锁,并将锁标志设为00,表示处于轻量级锁状态,此时状态图:

    在这里插入图片描述
    (5)如果更新失败,虚拟机会检查对象的Mark Word是否指向当前线程的栈帧,如果是则说明当前线程已经拥有这个锁,可进入执行同步代码。否则说明多个线程竞争,轻量级锁就会膨胀为重量级锁,Mark Word中存储重量级锁(互斥锁)的指针,后面等待锁的线程也要进入阻塞状态。

    五、重量级锁

    即当有其他线程占用锁时,当前线程会进入阻塞状态。
    synchronized

    六、自旋锁

    CAS,又称无锁
    在自旋状态下,当一个线程A尝试进入同步代码块,但是当前的锁已经被线程B占有时,线程A不进入阻塞状态,而是不停的空转,等待线程B释放锁。如果锁的线程能在很短时间内释放资源,那么等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞状态,只需自旋,等持有锁的线程释放后即可立即获取锁,避免了用户线程和内核的切换消耗。

    自旋等待最大时间:线程自旋会消耗cpu,若自旋太久,则会让cpu做太多无用功,因此要设置自旋等待最大时间。

    优点:开启自旋锁后能减少线程的阻塞,在对于锁的竞争不激烈且占用锁时间很短的代码块来说,能提升很大的性能,在这种情况下自旋的消耗小于线程阻塞挂起的消耗。

    缺点:在线程竞争锁激烈,或持有锁的线程需要长时间执行同步代码块的情况下,使用自旋会使得cpu做的无用功太多。

    JDK1.6中,设置参数 -XX:+UseSpinning开启。

    JDK1.7后,由JVM自动控制。

    七、锁升级过程

    在这里插入图片描述

    展开全文
  • 的状态总共有四种:无锁状态、偏向量级和重量级。随着的竞争,可以从偏向升级到量级,再升级的重量级(但是的升级是单向的,也就是说只能从低到高升级,不会出现的降级)
  • 基础知识 线程切换代价 Java的线程是映射到操作系统的原生线程之上的,如果阻塞或唤醒...JVM1.6之前,Synchronized会导致争不到的线程直接进入阻塞状态,所以说其是一个重量级的同步操作,被称为重量。 为了缓解上
  • 重量级量级 重量级量级 一般(一般一般一般) 类似于悲观和乐观,站在了不同的角度划分的。 量级又叫自旋。 重量级量级是站在 工作量 的角度来划分的; 而乐观和悲观锁则是站...
  • Synchronized升级的过程: 一个对象A刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程T1来访问它的时候,它会偏向T1,此时,对象A...
  • 原理:偏向、重量

    万次阅读 2017-12-18 16:15:16
    java中每个对象都可作为有四种级别,按照量级从轻到重分为:无锁、偏向量级、重量级。每个对象一开始都是无锁的,随着线程间争夺,越激烈,的级别越高,并且只能升级不能降级。
  • Java中的偏向量级, 重量级解析

    万次阅读 多人点赞 2018-08-13 18:39:49
    参考文章 聊聊并发(二)Java SE1.6中的Synchronized Lock Lock Lock: Enter! 5 Things You Didn’t Know About Synchronization in Java and Scala ...Java 中的 在 Java 中主要2种加锁机制: synchr...
  • 对象头:synchronized用的是存在Java对象头里的。Java对象头里的Mark Word里... 量级 => 重量级可以升级但是不能降级。升级的目的是为了提高获得和释放的效率。 偏向 当一个线程访问同步块获.
  • Java对象头 普通对象 ...当释放之后,另一线程获得资源,偏向升级为量级。 若还未释放,另一线程就来竞争资源,偏向直接升级为重量级。 调用hashcode方法,因为没有空间存储hashcod
  • 如果在运行过程中,遇到了其他线程抢占持有偏向的线程会被挂起,JVM会消除它身上的偏向,将恢复到标准的量级。 它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。 ...
  • synchronized原理,偏向量级,重量级升级
  • 一、Java中的分为两种:量级,重量级 量级,基z于CAS: 量级常见的两个问题: 1.ABA问题: 简单解释就是有一个基本类型a = 8,一个线程把他修改为a = 1,另一个线程有把他修改为a = 8,虽然前后...
  • 因为量级的获取及释放依赖多次CAS原子指令,而偏向只需要在置换ThreadID的时候依赖一次CAS原子指令。 1.2 执行流程 获取: 检测Mark Word是否为可偏向状态,即是否为偏向1,标识位为01; 若为可偏向...
  • 很多人都会称呼它为重量级,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,Java SE1.6中为了减少获得和释放带来的性能消耗而引入的偏向量级,以及的存储结构和...
  • 偏向量级、重量级解析

    千次阅读 2021-02-19 21:22:17
    Java 对象头 在32位虚拟机和64位虚拟机的 Mark Word 所占用的字节大小不一样,32位虚拟机的 Mark Word 和 class Pointer ...偏向量级的状态转化及对象Mark Word的关系 偏向的获得和撤销流程 时序图 流程图
  • 量级、偏向、重量级详情

    千次阅读 热门讨论 2018-11-17 13:48:47
    这篇文章是上篇文章是否真的理解了偏向量级、重量级膨胀)、自旋消除、粗化,知道重偏向吗?的补充,对于偏向,网上有些对于它的原理解读过于简单,简单得似乎是错误的,最显眼的是对于Mark...
  • 偏向量级、重量级 因为早期Java版本的synchronized底层实现采取的是操作系统的互斥量,线程阻塞和唤醒的代价很大,性能较低,因此Java尝试在多线程竞争不那么激烈的情况下,降低的开销。 1、偏向 偏向...
  • 偏向量级和重量级之间的关系,首先打个比方:假设现在厕所只有一个位置,每个使用者都有打开门锁的钥匙。必须打开门锁才能使用厕所。 小明今天吃坏了东西需要反复去厕所,如果小明每次都要开锁就很...
  • 关于并发编程下的各种机制的简单介绍和总结
  • 会拿着Mark Word里存的ThreadID和当前的线程ID进行对比,如果一致(还是线程1获取对象),无需使用CAS来加锁、解锁; 如果不一致,也就是其他线程(如线程2)目前正持有这把或者曾经持有过这把 说目前正持有...
  • 上篇文章已经分析了Java对象头构成、源码及其对象头的调试,本篇将分析偏向量级、重量级的实现及其演变过程。由于涉及到c++源码,估计不少同学没兴趣看,因此重点多以图+源码辅助分析。 通过本篇文章,你...
  • 三、的优化 1、升级 2、粗化 3、消除 一、Synchronized使用场景 Synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致数据不安全,而Synchronized关键字就是用于代码同步。什么...
  • 我们都知道synchronized内部有四种状态,分别是:无锁、偏向量级和重量级,所以要搞懂这几种之间的变化我们得对synchronized有个大致的了解。 首先说一下synchronized在底层的实现,他是基于进入和退出...
  • JDK1.6为了减少获得和释放所带来的性能消耗,引入了“偏向”和“量级”,所以在JDK1.6里一共有四种状态,无锁状态,偏向状态,量级状态和重量级状态,它会随着竞争情况逐渐升级。可以升级但不...
  • synchronized中重量级、偏向量级的区别

    万次阅读 多人点赞 2019-08-06 20:29:36
    我们先介绍重量级,然后介绍优化后的量级和偏向。 0.对象头以及Mark Word 1重量级 重量级的synchronized有三个用法: 普通同步方法,的是当前实例对象。 静态同步方法,的是当前类的clas...
  • 随着的竞争,可以从偏向升级到量级,再升级的重量级(但是的升级是单向的,也就是说只能从低到高升级,不会出现的降级)。的状态保存在对象头的Mark Word中,以32位的JDK为例: (一)...
  • 但是后面改进了,引进了的四个状态,分别是无锁,偏向量级,重量级,而且是只能逐级膨胀的。 但是我刚接触的时候一直很纠结膨胀过程,后来搞明白了,现在抽个时间总结记录一下。 首先我们要知道,这几个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 96,662
精华内容 38,664
关键字:

则轻锁