synchronized优化详解

JDK1.5提供了Lock锁
   Lock是一个接口,常用子类ReentrantLock
   使用Lock锁必须在finally块里释放锁
synchronized优化
   悲观锁:假设每一次执行同步代码块均会产生冲突,所以当线程获取锁成功,会阻塞其他尝试获取该锁的线程
   乐观锁:假设所有线程访问共享资源时不会出现冲突。
   既然不会出现冲突自然就不会阻塞其他线程。线程不会出现阻塞状态。
   CAS操作:compare and swap比较交换机制---乐观锁策略;无锁操作
   使用CAS叫做比较交换来判断是否出现冲突,出现冲突就重试当前操作直到不冲突为止。
       1.操作过程:一般来说,CAS(V【内存中地址存放的实际值】,O【预期值,旧值】,N【更新后的值】)
       当执行CAS后,如果V==O,即旧值于内存中实际值相等,表示上次修改该值后没有任何线程再次修改此值,因此可以将N替换到内存中。
       如果V!=O,表示该内存中的值已经被其他线程作了修改,所以无法将N替换,返回最新的值V。
       当多个线程使用CAS操作同一个变量时,只有一个线程会成功,并成功更新变量值,其余线程均会失败,失败线程会重新尝试或将线程挂起(阻塞)
       CAS的实现需要硬件指令集的支撑

   元老级内建锁(synchronized)最主要的问题:当存在线程竞争的情况下会出现线程阻塞以及唤醒带来的性能问题,对应互斥同步(阻塞同步),效率很低
   而CAS不是武断地将线程挂起,会尝试若干次CAS操作,并非进行耗时的挂起与唤醒操作,因此非阻塞式同步。
       2. (自旋)CAS问题
         2.1 ABA问题 解决思路:沿用数据库乐观锁机制,添加版本号1A-2B-3A
         JDK1.5提供atomic包下AtomicStampedReference类来解决CAS的ABA问题
         2.2 自旋会浪费大量的处理器资源(CPU)
         与线程阻塞相比,自旋会浪费大量的CPU资源。因为此时线程仍然处于运行状态,只不过跑的是无用指令,期望在无用指令时,锁能被释放出来。
         解决思路:自适应自旋(根据以往自旋等待时能否获取到锁,来动态调整自旋的时间(循环尝试数量))
         如果在上一次自旋时获取到锁,则此次自旋时间稍微变长一点;如果在上一次自旋结束还没有获取到锁,此次自旋时间稍微短一点。
         2.3 公平性问题
         出于阻塞状态的线程无法立刻竞争被释放的锁;而处于自旋状态的线程很有可能先获取到锁。
         内建锁无法实现公平性
         lock体系可以实现公平锁
Java对象头
类似于对对象的一个标志,那么这个标志就是存放在Java对象的对象头。
   JDK1.6之后对内建锁做了优化(新增偏量、轻量级锁)
   锁状态在对象头的mark Word中,mark word默认存放的是对象的Hashcode,分代年龄和锁标记位。
   无锁状态0 01

   偏向锁1 01:最乐观的锁,从始至终只有一个线程请求一把锁。为了减少获得锁的效率。
   1)偏向锁的获取:当一个线程访问同步代码块并且获取锁时,会在对象头和栈帧中的锁记录中记录存储偏向锁的线程ID。
   以后该线程再次进入同步块时不需要CAS来加锁解锁,只需要简单测试一下对象头的mark word中偏向锁线程ID是否是当前线程ID,
   如果成功,表示县城已经获取到锁直接进入代码块运行。
   如果测试失败,检查当前偏向锁阶段是否为0,如果为0,将偏向锁字段设置为1并且更新自己的线程ID到mark word字段中。
   如果为1表示此时偏向锁已经被别的线程获取。则此线程1)不断尝试使用CAS获取偏向锁或者2)将偏向锁撤销,升级为轻量级锁(升级概率较大)
   2)偏向锁的撤销:
   偏向锁使用一种等待竞争出现才释放锁的机制,当有其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放偏向锁。
   小提示:偏向锁的撤销开销比较大,需要等待线程进入全局安全点safepoint(当前线程在CPU上没有执行任何有用字节码)
   偏向锁从JDK1.6之后默认开启,但是他在应用程序启动几秒后才激活
   -XX:BiasedLockingStartupDelay=0,将延迟关闭,JVM一启动就激活偏向锁。
   -XX:BiasedLocking=false,关闭偏向锁,程序默认进入轻量级锁。
   Epoch偏向锁的撤销次数,默认到40的时候就不会再有偏向锁,升级为轻量级锁。

   轻量级锁00
   多个线程在不同的时间段请求同一把锁,也就是基本不存在锁竞争,针对这种状况,JVM采用轻量级锁来避免线程阻塞以及唤醒。
   加锁:线程在执行同步代码块之前,JVM现在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的mark word字段直接复制到此空间中。
   然后线程尝试使用CAS将对象头的mark word替换为指向锁记录的指针(指向当前线程),如果成功表示获取到轻量级锁。
   如果失败,表示其他线程竞争轻量级锁,当前线程便使用自旋来不断尝试。
   释放:解锁时,会使用CAS将复制的mark word替换回对象头,如果成功,表示没有竞争发生,正常解锁。
   如果失败,表示当前锁存在竞争,进一步膨胀为重量级锁。(只要多个线程在同一时刻竞争同一个锁一定会膨胀为重量级锁)

   重量级锁(JDK1.6之前)10
   重量级锁会阻塞、唤醒请求加锁的线程。针对的是多个线程同时竞争同一把锁的情况,JVM采用自适应自旋,来避免线程在面对非常小的同步块时,仍会被阻塞以及唤醒。

   轻量级锁采用CAS操作,将锁对象的标记字段替换为指向线程的指针,存储着锁对象原本的标记字段。
   针对的是多个线程在不同时间段申请同一把锁的情况。

   偏向锁只会在第一次请求时采用CAS操作,在锁对象的mark_word字段中记录下当前线程的ID,此后运行中持有偏向锁的线程不再有加锁过程。
   针对的是,锁仅会被同一线程所持有。

   11是GC标记(是否释放锁)
   这四种状态随着竞争情况逐渐升级,锁可以升级不能降级,为了提高获得锁与释放锁的效率。

猜你喜欢

转载自blog.csdn.net/qq_43577380/article/details/89416366