精华内容
下载资源
问答
  • Java偏向锁和轻量级锁

    2018-06-30 15:32:29
    Java SE 1.6对锁进行了大量优化工作,引入轻量级锁和偏向锁,这里只对这两种优化作说明。Java SE 1.6中的锁状态分为无锁、偏向锁、轻量锁、重量锁4种,级别依次递增,性能越来越差,且升级后不允许降级,这么做能在...

        Java SE 1.6对锁进行了大量优化工作,引入轻量级锁和偏向锁,这里只对这两种优化作说明。Java SE 1.6中的锁状态分为无锁、偏向锁、轻量锁、重量锁4种,级别依次递增,性能越来越差,且升级后不允许降级,这么做能在多数环境下有利于锁的获取和释放。

        我们知道Java对象的头部是记录了对象的相关信息,具体情况看我另外文章,这里放张图好对照:

    上图是对象头中Mark Word信息,锁的相关信息存放在这里。另外,有几点前提需要说明下,线程在执行同步代码之前,JVM会首先在当前线程的栈帧中创建用于存储锁记录的空间,然后将对象头中的Mark Word复制到线程栈帧中,

        具体操作过程是当某个线程访问同步块时,并不是直接去竞争锁,首先判断偏向锁相关信息,当然前提是JVM开启了偏向锁,可以通过-XX:-UseBiasedLocking=false来关闭偏向锁,关闭偏向锁后,JVM默认进入轻量级锁状态。在开启偏向锁的环境下,线程进入同步块时,会首先读取对象头中偏向线程ID是否和当前线程ID一致,如果一致,直接得到锁,如果不一致,需要读取对象头中偏向锁标记是否=1,如果是(是偏向锁),使用CAS设置对象头中偏向锁线程ID为当前线程,并得到锁,如果对象头中偏向锁标记没有设置,使用CAS竞争锁。偏向锁的撤销过程如下:先暂停持有偏向锁的线程,然后检查目标线程存活与否,如果死了,直接设置对象头偏向线程ID为空,和偏向锁标记,也即无锁状态,如果目标线程还存活,设置对象头使其偏向其他线程或设置对象头为无锁状态或设置对象头为不合适偏向锁状态,最后唤醒暂停的线程。

    为什么要拷贝Mark word,是不想在lock和unlock这种操作上再加锁,Mark word的锁指针指向lock record是为了让其他线程知道该object monitor已经被占用,lock record的owner指向Mark word是为了在同步块的执行中,知道哪个object被锁住了。轻量级锁主要细分为自旋锁和自适应自旋锁。

     

    展开全文
  • java 偏向锁轻量级锁及重量级锁synchronized原理 Java对象头与Monitor java对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的。 对象头包含两部分:Mark Word Class ...

    今天简单了解了一下java轻量级锁和重量级锁以及偏向锁。看了看这篇文章觉得写的不错
    原文链接

    java 偏向锁、轻量级锁及重量级锁synchronized原理

    Java对象头与Monitor

    java对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的。

    对象头包含两部分:Mark Word 和 Class Metadata Address

    在这里插入图片描述

    其中Mark Word在默认情况下存储着对象的HashCode、分代年龄、锁标记位等以下是32位JVM的Mark Word默认存储结构

    在这里插入图片描述

    由于对象头的信息是与对象自身定义的数据没有关系的额外存储成本,因此考虑到JVM的空间效率,Mark Word 被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下,除了上述列出的Mark Word默认存储结构外,还有如下可能变化的结构:

    在这里插入图片描述

    重量级锁synchronized的实现
    重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

    在这里插入图片描述

    上图简单描述多线程获取锁的过程,当多个线程同时访问一段同步代码时,首先会进入 Entry Set当线程获取到对象的monitor 后进入 The Owner 区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

    由此看来,monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因。

    自旋锁与自适应自旋
    Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一个线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间,对于代码简单的同步块(如被synchronized修饰的getter()和setter()方法),状态转换消耗的时间有可能比用户代码执行的时间还要长。

    虚拟机的开发团队注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下“,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

    自旋锁在JDK1.4.2中引入,使用-XX:+UseSpinning来开启。JDK1.6中已经变为默认开。自旋等待不能代替阻塞。自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长,那么自旋的线程只会浪费处理器资源。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当使用传统的方式去挂起线程了。

    JDK1.6中引入自适应的自旋锁,自适应意味着自旋的时间不在固定。而是有虚拟机对程序锁的监控与预测来设置自旋的次数。

    自旋是在轻量级锁中使用的

    轻量级锁
    轻量级锁提升程序同步性能的依据是:对于绝大部分的锁,在整个同步周期内都是不存在竞争的(区别于偏向锁)。这是一个经验数据。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。

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

    在这里插入图片描述

    拷贝对象头中的Mark Word复制到锁记录(Lock Record)中;

    拷贝成功后,虚拟机将使用CAS操作尝试将锁对象的Mark Word更新为指向Lock Record的指针,并将线程栈帧中的Lock Record里的owner指针指向Object的 Mark Word。
    如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图所示。
    在这里插入图片描述

    如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

    在这里插入图片描述

    偏向锁
    偏向锁是JDK6中引入的一项锁优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。

    偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

    当锁对象第一次被线程获取的时候,线程使用CAS操作把这个线程的ID记录在对象Mark Word之中,同时置偏向标志位1。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的ID。如果测试成功,表示线程已经获得了锁。

    当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。
    在这里插入图片描述

    偏向所锁,轻量级锁及重量级锁
    偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。
    一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个
    线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将
    对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
    一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
    轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

    展开全文
  • 1 前言 在Java SE1.6之前,synchronized一直都是重量级锁,如果某个...在Java SE1.6之后,为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。这些锁就存在锁对象的对象...

    目录

    1 前言

    2 偏向锁

    2.1 锁获取过程

    2.2 锁撤销过程

    2.3 偏向锁的关闭

    3 轻量级锁

    3.1 轻量级锁获取过程

    3.2 轻量级锁释放过程

    4 重量级锁

    5 锁的优缺点对比


    1 前言

    在Java SE1.6之前,synchronized一直都是重量级锁,如果某个线程获得了锁,其它获取锁的线程必须阻塞。在高并发的情况下,会有大量线程阻塞,导致系统响应速度急剧下降;同时不断的获取和释放锁也会导致线程不断切换,给系统造成较大的负担。在Java SE1.6之后,为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。这些锁就存在锁对象的对象头中,可以参考:Synchronized与Java对象头

    在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。

    2 偏向锁

    synchronized锁最初的锁等级就是偏向锁。

    2.1 锁获取过程

    HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

    当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁;如果测试失败,要检查当前锁是否是偏向锁,即测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果不是偏向锁,则使用CAS竞争锁;如果是偏向锁,表示锁已经被其它线程持有,则持有锁的线程释放锁,并尝试使用CAS将对象头的偏向锁指向当前线程(即当前线程获得锁)

    2.2 锁撤销过程

    当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

    2.3 偏向锁的关闭

    偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

    3 轻量级锁

    当偏向锁出现竞争时,锁就会升级成轻量级锁。如果关闭了偏向锁,那么一开始锁就是轻量级锁。

    3.1 轻量级锁获取过程

    线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中(官方称为Displaced Mark Word),然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程已获取锁,当前线程便尝试使用自旋来获取锁

    3.2 轻量级锁释放过程

    轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头。如果成功,则表示没有竞争发生;如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

    4 重量级锁

    轻量级锁替换失败到达一定次数(默认为10)后,轻量级锁升级为重量级锁。如果线程2自旋期间,有线程3也需要访问同步方法,则立刻由轻量级锁膨胀为重量级锁。

      java1.6中,引入了自适应自旋锁,自适应意味着自旋 的次数不是固定不变的,而是根据前一次在同一个锁上自 旋的时间以及锁的拥有者的状态来决定。 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。

    因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

    5 锁的优缺点对比

    优点 缺点 适用场景
    偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步块场景
    轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 如果始终得不到锁竞争的线程,使用自旋会消耗CPU 追求响应时间,同步块执行速度非常快
    重量级锁 线程竞争不使用自旋,不会消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,同步块执行速度较长

    6 面试应答

    Java中为了提高锁的性能,对锁进行了分类,使它们分别适用于不同的场景。

    偏向锁

    • 获取锁:偏向锁是最低等级的锁,当第一个线程进入同步代码块时,对象头的Mark Word中锁的类型就是偏向锁,同时Mark Word中存储指向当前获取锁的线程的ID,表示该线程获取了锁。
    • 释放(升级)锁:当另一个线程访问同步代码块时,等当前线程执行完同步代码,锁会被释放并升级为轻量级锁;如果没有线程访问,线程会一直持有锁。
    • 应用场景:偏向锁适用于单线程的场景。一个线程频繁的获取锁时,不需要使用CAS操作来加锁和解锁,只要判断Mark Word中的线程ID是不是自己的ID即可,这和执行非同步方法的效率差不多。

    轻量级锁

    • 获取锁:线程在执行同步代码块前,会先将对象头中的Mark Word复制到自己的栈帧中。然后使用CAS操作将对象头中的Mark Word变成指向自己栈帧中Mark Word的指针,修改成功便获得锁,失败就说明有竞争,然后通过自旋来获取锁
    • 释放锁:通过CAS操作将自己栈帧中的Mark Word替换会对象头的Mark Word中,如果失败,表示锁存在竞争,锁会膨胀成重量级锁;成功就说明没竞争。问题:为什么释放锁还会失败?????又不是有多个线程一起释放。我的理解是轻量级锁中,当前线程释放锁并不是其它线程获取锁的充分条件
    • 应用场景:适用于同步代码执行时间较短的场景,响应速度快。因为竞争的线程是通过自旋来继续竞争锁,不会阻塞,所以持有锁的线程快速执行完后能立即切换到自旋线程执行,避免了阻塞情况下的线程切换。

    重量级锁

    • 获取锁:同轻量级锁,不过失败时会直接阻塞,不是自旋。
    • 释放锁:
    • 应用场景:适用于同步代码执行时间长的场景,吞吐量高(能接受很多的请求,不过大部分都是阻塞状态)。

     

    展开全文
  • 三、的优化 1、升级 2、粗化 3、消除 一、Synchronized使用场景 Synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致数据不安全,而Synchronized关键字就是用于代码同步。什么...

    目录

    一、Synchronized使用场景

    二、Synchronized实现原理

    三、锁的优化

    1、锁升级

    2、锁粗化

    3、锁消除


    一、Synchronized使用场景

    Synchronized是一个同步关键字,在某些多线程场景下,如果不进行同步会导致数据不安全,而Synchronized关键字就是用于代码同步。什么情况下会数据不安全呢,要满足两个条件:一是数据共享(临界资源),二是多线程同时访问并改变该数据。

    例如:

    public class AccountingSync implements Runnable{
        //共享资源(临界资源)
        static int i=0;
    
        /**
         * synchronized 修饰实例方法
         */
        public synchronized void increase(){
            i++;
        }
        @Override
        public void run() {
            for(int j=0;j<1000000;j++){
                increase();
            }
        }
        public static void main(String[] args) throws InterruptedException {
            AccountingSync instance=new AccountingSync();
            Thread t1=new Thread(instance);
            Thread t2=new Thread(instance);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    }

    该段程序的输出为:2000000

    但是如果increase的synchronized被删除,那么很可能输出结果就会小于2000000,这是因为多个线程同时访问临界资源i,如果一个线程A对i=88的自增到89没有被B线程读取到,线程B认为i仍然是88,那么线程B对i的自增结果还是89,那么这里就会出现问题。

    Synchronized锁的3种使用形式(使用场景):

    • Synchronized修饰普通同步方法:锁对象当前实例对象;
    • Synchronized修饰静态同步方法:锁对象是当前的类Class对象;
    • Synchronized修饰同步代码块:锁对象是Synchronized后面括号里配置的对象,这个对象可以是某个对象(xlock),也可以是某个类(Xlock.class);

    注意:

    • 使用synchronized修饰非静态方法或者使用synchronized修饰代码块时制定的为实例对象时,同一个类的不同对象拥有自己的锁,因此不会相互阻塞。
    • 使用synchronized修饰类和对象时,由于类对象和实例对象分别拥有自己的监视器锁,因此不会相互阻塞。
    • 使用使用synchronized修饰实例对象时,如果一个线程正在访问实例对象的一个synchronized方法时,其它线程不仅不能访问该synchronized方法,该对象的其它synchronized方法也不能访问,因为一个对象只有一个监视器锁对象,但是其它线程可以访问该对象的非synchronized方法。
    • 线程A访问实例对象的非static synchronized方法时,线程B也可以同时访问实例对象的static synchronized方法,因为前者获取的是实例对象的监视器锁,而后者获取的是类对象的监视器锁,两者不存在互斥关系。

     

    二、Synchronized实现原理

    1、Java对象头

    首先,我们要知道对象在内存中的布局:

    已知对象是存放在堆内存中的,对象大致可以分为三个部分,分别是对象头、实例变量和填充字节。

    • 对象头的zhuyao是由MarkWord和Klass Point(类型指针)组成,其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据。如果对象是数组对象,那么对象头占用3个字宽(Word),如果对象是非数组对象,那么对象头占用2个字宽。(1word = 2 Byte = 16 bit)
    • 实例变量存储的是对象的属性信息,包括父类的属性信息,按照4字节对齐
    • 填充字符,因为虚拟机要求对象字节必须是8字节的整数倍,填充字符就是用于凑齐这个整数倍的

    通过第一部分可以知道,Synchronized不论是修饰方法还是代码块,都是通过持有修饰对象的锁来实现同步,那么Synchronized锁对象是存在哪里的呢?答案是存在锁对象的对象头的MarkWord中。那么MarkWord在对象头中到底长什么样,也就是它到底存储了什么呢?

    在32位的虚拟机中:

    在64位的虚拟机中:

    上图中的偏向锁和轻量级锁都是在java6以后对锁机制进行优化时引进的,下文的锁升级部分会具体讲解,Synchronized关键字对应的是重量级锁,接下来对重量级锁在Hotspot JVM中的实现锁讲解。

    2、Synchronized在JVM中的实现原理

    重量级锁对应的锁标志位是10,存储了指向重量级监视器锁的指针,在Hotspot中,对象的监视器(monitor)锁对象由ObjectMonitor对象实现(C++),其跟同步相关的数据结构如下:

    ObjectMonitor() {
        _count        = 0; //用来记录该对象被线程获取锁的次数
        _waiters      = 0;
        _recursions   = 0; //锁的重入次数
        _owner        = NULL; //指向持有ObjectMonitor对象的线程 
        _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
      }
    

    光看这些数据结构对监视器锁的工作机制还是一头雾水,那么我们首先看一下线程在获取锁的几个状态的转换:

    线程的生命周期存在5个状态,start、running、waiting、blocking和dead

    对于一个synchronized修饰的方法(代码块)来说:

    1. 当多个线程同时访问该方法,那么这些线程会先被放进_EntryList队列,此时线程处于blocking状态
    2. 当一个线程获取到了实例对象的监视器(monitor)锁,那么就可以进入running状态,执行方法,此时,ObjectMonitor对象的_owner指向当前线程,_count加1表示当前对象锁被一个线程获取
    3. 当running状态的线程调用wait()方法,那么当前线程释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1,同时线程进入_WaitSet队列,直到有线程调用notify()方法唤醒该线程,则该线程重新获取monitor对象进入_Owner区
    4. 如果当前线程执行完毕,那么也释放monitor对象,进入waiting状态,ObjectMonitor对象的_owner变为null,_count减1

    那么Synchronized修饰的代码块/方法如何获取monitor对象的呢?

    在JVM规范里可以看到,不管是方法同步还是代码块同步都是基于进入和退出monitor对象来实现,然而二者在具体实现上又存在很大的区别。通过javap对class字节码文件反编译可以得到反编译后的代码。

    (1)Synchronized修饰代码块:

    Synchronized代码块同步在需要同步的代码块开始的位置插入monitorentry指令,在同步结束的位置或者异常出现的位置插入monitorexit指令;JVM要保证monitorentry和monitorexit都是成对出现的,任何对象都有一个monitor与之对应,当这个对象的monitor被持有以后,它将处于锁定状态。

    例如,同步代码块如下:

    public class SyncCodeBlock {
       public int i;
       public void syncTask(){
           synchronized (this){
               i++;
           }
       }
    }
    

    对同步代码块编译后的class字节码文件反编译,结果如下(仅保留方法部分的反编译内容):

      public void syncTask();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter  //注意此处,进入同步方法
             4: aload_0
             5: dup
             6: getfield      #2             // Field i:I
             9: iconst_1
            10: iadd
            11: putfield      #2            // Field i:I
            14: aload_1
            15: monitorexit   //注意此处,退出同步方法
            16: goto          24
            19: astore_2
            20: aload_1
            21: monitorexit //注意此处,退出同步方法
            22: aload_2
            23: athrow
            24: return
          Exception table:
          //省略其他字节码.......

    可以看出同步方法块在进入代码块时插入了monitorentry语句,在退出代码块时插入了monitorexit语句,为了保证不论是正常执行完毕(第15行)还是异常跳出代码块(第21行)都能执行monitorexit语句,因此会出现两句monitorexit语句。

    (2)Synchronized修饰方法:

    Synchronized方法同步不再是通过插入monitorentry和monitorexit指令实现,而是由方法调用指令来读取运行时常量池中的ACC_SYNCHRONIZED标志隐式实现的,如果方法表结构(method_info Structure)中的ACC_SYNCHRONIZED标志被设置,那么线程在执行方法前会先去获取对象的monitor对象,如果获取成功则执行方法代码,执行完毕后释放monitor对象,如果monitor对象已经被其它线程获取,那么当前线程被阻塞。

    同步方法代码如下:

    public class SyncMethod {
       public int i;
       public synchronized void syncTask(){
               i++;
       }
    }

    对同步方法编译后的class字节码反编译,结果如下(仅保留方法部分的反编译内容):

    public synchronized void syncTask();
        descriptor: ()V
        //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED
        Code:
          stack=3, locals=1, args_size=1
             0: aload_0
             1: dup
             2: getfield      #2                  // Field i:I
             5: iconst_1
             6: iadd
             7: putfield      #2                  // Field i:I
            10: return
          LineNumberTable:
            line 12: 0
            line 13: 10
    }
    

    可以看出方法开始和结束的地方都没有出现monitorentry和monitorexit指令,但是出现的ACC_SYNCHRONIZED标志位。

    三、锁的优化

    1、锁升级

    锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)

    (1)偏向锁:

    为什么要引入偏向锁?

    因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

    偏向锁的升级

    当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程

    偏向锁的取消:

    偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0

    如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;

    (2)轻量级锁

    为什么要引入轻量级锁?

    轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

    轻量级锁什么时候升级为重量级锁?

    线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址

    如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁

    但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

     

    *注意:了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

    (3)这几种锁的优缺点(偏向锁、轻量级锁、重量级锁)

    2、锁粗化

    按理来说,同步块的作用范围应该尽可能小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,缩短阻塞时间,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。 
    但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗。 
    锁粗化就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。

    3、锁消除

    Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,经过逃逸分析,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间

    展开全文
  • 1,偏向锁偏向锁:顾名思义,偏心与某一个线程锁,而他偏心的线程就是第一个访问该锁的线程,在某个锁第一次被一个线程所访问的时候,该锁会在她的Mark Word中记录该线程的线程id,而在该线程第二次去访问这个锁...
  • 【笔记】java中的所有锁 锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和...JDK 1.6中默认是开启偏向锁和轻量级锁的。 锁膨胀:从轻量锁膨胀到重量级锁是在轻量级锁解锁过程发生的。 四种状态会随着竞争的情...
  • 最近因为工作关系遇到了很多Java并发编程的问题,然后恶补了一下,现在就来说说Java目前的锁实现原理 其实在JDK1.5以前的早期版本,还没有...偏向锁和轻量级锁都属于乐观锁,偏向锁指的是没有其他线程竞争资源,只...
  • Synchronized一直是多线程并发编程中的重要角色,但是在Java1.6中,为了减少获得锁带来的性能消耗,引入了偏向锁和轻量级锁。 目录 锁的状态: 偏向锁 轻量级锁 重量级锁 偏向锁、轻量级锁、重量级锁应用场景 ...
  • Java多线程偏向锁轻量级锁,公平锁和非公平锁,互斥锁和共享锁 ** 前期知识储备: **在分析以下各种锁之前我们需要了解Java对象结构与对象头中的MarkWord标记字。 1、Java对象存在于堆内存当中,一个对象包括对象...
  • 锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)
  • 在学习sychronized关键字及其实现细节的时候,发现java中的三种锁,偏向锁轻量级锁,重量级锁其实也有很多值得探究的地方,引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级...
  • JDK1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在JDK1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不...
  • 从synchronized举例,在java1.6当中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。 ·对于普通同步方法,锁是当前实例对象。 ·对于静态同步方法,锁是当前类的...
  • Synchronized偏向锁和轻量级锁的升级 一、Synchronized实现原理 1、Synchronized锁的3中形式 利用 synchronized 实现同步的基础:Java 中的每一个对象都可以作为锁。具体表现为以下3种形式。 对于普通同步方法,锁...
  • 前言:java的线程是映射到操作系统的原生线程上的,如果要阻塞唤醒线程,需要操作系统帮忙,要从用户态转为核心态,需要花费很多处理器时间。
  • 锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高) (1)偏向锁: 为什么要引入偏向锁? 因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次...
  • 思考:如何理解和证明偏向锁和轻量级锁和重量级锁的存在的 首先了解下java中一个对象的组成结构 一个对象存在内存中是由三个部分组成的: 1、mark word 2、指向类的指针 3、对齐填充字节 链接: java中对象的组成 ...
  • 本章所需基础知识: ...B站马士兵老师的视频:无锁、偏向锁轻量级锁、重量级锁的锁升级 本文章目录: 无锁、偏向锁轻量级锁、重量级锁的概念 可重入锁与不可重入锁 深山猿的博客讲的很好 ...
  • 重量级锁 Java的对象头中的MarkWord 32位操作系统: 64位操作系统: Monitor 当使用synchronized获得锁时: synchronized(obj){//重量级锁 //临界区代码 } obj对象的MarkWord中的指针(ptr_to_heavyweight_...
  • 为了减少获得锁和释放锁带来的性能消耗,java引入了偏向锁和轻量级锁,并对锁的存储结构进行了升级。 synchronized在JVM中的实现原理 JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,monitorenter...
  • 偏向锁 偏向锁是JDK1.6提出来的一种锁优化的机制。其核心的思想是,如果程序没有竞争,则取消之前已经取得锁的线程同步操作。...如果偏向锁失败,Java虚拟机就会让线程申请轻量级锁轻量级锁在虚拟机...
  • Java--偏向锁/轻量级锁/重量级锁

    千次阅读 2018-04-10 14:34:01
    另外偏向锁---轻量级锁---重量级锁本文都需要依托synchronize进行理解分析。另外也要参照网络上很多的资料。1.对象头:关于对象头的具体记录,可以参考这边:对象头2.同步的原理:关于JVM规范对于同步的解释,可以...
  • 轻量级锁: 基于CAS 自旋 偏向锁:对象第一次被线程使用; 锁自旋: 循环执行CAS 公平锁 非公平锁 sync Lock的区别 synchronized是关键字,是JVM层面的底层啥都帮我们做了,而Lock是一个接口,是JDK层面的有...
  • 在JDK1.6之后synchronized 的效率已经JUC中的ReentenerLock效率相差无几,在深入理解java虚拟机中也有提到。那么jvm团队做了哪些优化呢? 1.基础 利用synchronized实现同步的基础:Java中的每一个对象都可以作为...
  • 我们先介绍重量级锁,然后介绍优化后的轻量级锁和偏向锁。 0.对象头以及Mark Word 1重量级锁 重量级的synchronized有三个用法: 普通同步方法,锁的是当前实例对象。 静态同步方法,锁的是当前类的clas...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 876
精华内容 350
关键字:

java偏向锁和轻量级锁

java 订阅