暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

锁--悲观锁的代表synchronized

青阳大君 2021-07-30
1416

    上文中多线程源码中用到ReentrantLock对象,有过锁相关方面知识的开发者多多少少有了解过。网上关于锁的知识,丰富而又杂乱。本篇文章,我们就来聊聊java语言JUC包中非常重要的一个知识章节--锁。

    就算是初入门开发者没有刻意学习过JUC包中关于锁的模块也知道synchronized这个关键字。

      synchronized 在jdk 1.6后引入了偏向锁,轻量级锁的概念。为什么要引入这两个概念,根据开发的经验所知,70-80%的开发中都不会出现多个线程竞争锁,所以如果synchronized只存在重量级锁,由于重量级锁运行在系统核心区,资源切换消耗大,浪费性能。引入偏向锁和轻量级锁就是为了让synchronized 更轻。


synchronized 锁的膨胀顺序:无状态锁->偏向锁->轻量级锁->重量级锁


(本图来自转载:https://www.topjava.cn/article/139131106823150387)




上述图描述的是线程在竞争锁时,jvm中的markword是怎样通过标志位的变化来控制jvm中锁的膨胀。


markword:

32位 MarkWord格式


64位 MarkWord格式



锁的优缺点对比以及使用场景对比

优点缺点使用场景
偏向锁加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比差距忽略不计如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步快的场景
轻量级锁竞争的线程不会阻塞,提高了响应速度如线程成始终得不到锁竞争的线程,使用自旋会消耗CPU性能追求响应时间,同步快执行速度非常快,存在两个及两个以上的线程竞争时存在
重量级锁线程竞争不适用自旋,不会消耗CPU线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗

当线程自旋默认的10次,或者等待线程数超过CPU核数的一半时,升级为重量级锁。追求吞吐量,同步快执行速度较长



jdk1.6中对jvm中锁的实现引入了大量的优化,除了上述的偏向锁、轻量级锁。还增加了如锁粗化(Lock Coarsening)、锁消除(Lock Elimination),以及适应性锁自旋


锁粗化:java中定义StringBuilder 对象A,并对A多次append,jvm为了不必要的多个锁,将多个锁合并成一个锁操作,就是锁粗化的典型案例;


锁消除:在一个方法中定义多个加锁操作,经过逃逸分析(同步块中的对象没有被其他的线程对象引用,线程中对象安全)之后,会将不必要的锁消除掉;


适应性自旋:轻量级锁的场景中,其他线程在等待获得锁的线程释放的过程中,有些线程能够获得释放的锁,那么jvm会增加此线程下次等待的时间,反之,会减少甚至省略掉此过程,避免浪费资源。


synchronized 在开发中,常在以下四种情况下会使用:

1、加在类上 :其作用范围是synchronized后面括号括起来的部分,作用对象是这个类的所有对象。

2、加在方法上:被修饰的方法称为同步方法,作用范围整个方法,作用的对象是调用这个方法的对象。

3、加在静态方法上:作用范围整个静态方法,作用的对象是这个类的所有对象。

4、同步代码块上:被修饰的代码块称为同步代码块,作用的对象是调用这个代码块的对象。


被synchronized上锁的临界资源,会在jvm中自动被回收,而且无法主动回收。

可以将一段synchronized同步代码块编译后,用javap -c xxx.class 文件,会得到下面的截图:



    monitor是jvm的监视器,线程成功获取锁后,monitorenter成功,则从阻塞同步队列中remove掉,并且锁计数器+1,synchronized可重入性设计就是通过增加当前线程的锁计数方式实现,一段代码中对同步块多增加一次synchronized,则此线程的锁计数器+1.

    synchronized的重入特性证明:已经被synchronized修饰过的同步代码块再加一段被synchronized修饰过的静态代码块,反编译后可发现monitorenter是不会出现的。

    释放锁机制:释放一次锁计数器就会减1,直到减到0,此时锁会被释放掉。上图中有两个monitorexit,第二个就是jvm内部对锁的释放。所以被synchronized修饰的同步块无需自己释放锁。



总结:说到这里,基本上synchronized这个在jvm中运行的锁,大概说的差不多了(有兴趣的可以继续深入源码)。通过上面的特性可知,synchronized定义的锁并不能在高并发的场景下应用。synchronized定义的锁广义上是一种悲观锁。所谓悲观锁,就是线程执行前,就认为写的线程远多于读的线程,线程在试图获取临界资源时首先给它加锁,在高并发场景下,这种同步阻塞队列方式,线程的上下文切换,线程执行从用户态切换到核心态等等操作太影响线程执行的效率。所以此时JUC大神doug lee在设计JUC核心包的时候引入了乐观锁的概念。


关于乐观锁,以及文中提到的CAS等关键字下面章节再做详细介绍,敬请期待。

文章转载自青阳大君,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论