查看原文
其他

Synchronized 实现原理,看这篇就对了!

点击上方 "程序员小乐"关注公众号, 星标或置顶一起成长

每天早上8点20分, 第一时间与你相约

每日英文

Sometimes you have to accept the fact that certain things will never go back to how they used to be.

有时候,你不得不接受这个现实:有些事情已回不到从前了。


每日掏心话

不要那么敏感,也不要那么心软,太敏感和太心软的人,肯定过得不快乐,别人随便的一句话,你都要胡思乱想一整天。


来自:奋进的小样 | 责编:乐乐

链接:cnblogs.com/fenjyang/p/11594556.html

程序员小乐(ID:study_tech)第 643 次推文   图片来自网络


往日回顾:悲痛!Facebook中国程序员疑遭印裔上司压榨跳楼,年仅38岁,浙大毕业,去年刚入职



   01 前言   


Synchronized 在多线程环境下是不可缺少的,那么对于Synchronized 又了解多少呢。下面就系统总结,而对于Synchronized的基本使用,请参看另一篇博客。

1.1 Synchronized 作用


  • 确保线程互斥的访问同步代码

  • 保证共享变量的修改能够及时可见

  • 有效解决重排序问题



   02 从JVM理解Synchronized   


首先使用JDK自带的反编译工具查看Synchronized编译后的字节码,打开cmd进入到.class文件所在文件目录,输入javap -v 类名.class

先看如下代码:

package com.mult;
public class Demo { private static int value = 10; public static void main(String[] args) { System.out.println(new Demo().method()); } public synchronized int method() { synchronized (Demo.class) { if (value > 5) { return value; } else { return 0; } } }}

从上图可以看出Synchronized 是通过monitorenter和monitorexit两个字节码指令实现的。在每一个对象中都会存在一个Monitor监视器,而monienter和monitorexit两者之间是互斥关系,monienter用于获取对象锁,而moniexit释放对象锁。

 在JVM规范文档中有以下说明:

  • 如果 Monitor 的计数器为 0,则该线程进入 Monitor,然后将计数器值设置为 1,该线程即为 Monitor 的所有者,也就是说此时获取到对象锁。

  • 如果线程已经占有该 Monitor,只是重新进入,则进入 Monitor 的计数器加 1。

  • 如果其他线程已经占用了 Monitor,则该线程进入阻塞状态,直到 Monitor 的计数器为 0,再重新尝试获取 Monitor 的所有权。

当计数器为0时,Monitor便会释放对象锁,那么其他阻塞的线程就可以尝试申请获取对象锁。

总结这里,就要引出另一个内容,就是Synchronized是可重入锁。


   03 可重入锁   


可重入锁: 一个线程已经获取到对象锁时,其他线程处于阻塞状态。但获取到对象锁的线程再次去请求自己所持有的对象锁资源时,这种情况成为可重入锁。

请看实例代码:

public class Demo { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { Demo demo = new Demo(); demo.method_1(); } }).start(); } public synchronized void method_1() { System.out.println(Thread.currentThread().getName()+"-->method_1...."); method_2(); } public synchronized void method_2() { System.out.println(Thread.currentThread().getName()+"-->method_2...."); }}

 

以上代码中只有一个demo对象锁,在method_1中调用method_2结果依然可以打印,证明Synchronized是可重入锁。反之,如果不是可重入锁,那么在method_1中获取到对象锁,接着调用method_2便会产生死锁,另外两个方法的线程名称是相同的,也可以证明该线程拿到的就是同一个对象锁。

注意:当子类继承父类时,子类也是可以通过可重入锁调用父类的同步方法。



   04 锁的优化   


在JDK6之后,对Synchronized的实现进行了优化,引入了偏向锁、轻量级锁,锁,它们之间的关系为;

无锁->偏向锁->轻量级锁->重量级锁

注意:以上级别之间的转换是单向的,只能从低级转向高级,反之不可。

4.1 偏向锁

在某一环境下,一个线程可能会多次获得对象锁。那么频繁的申请锁释放锁势必会对性能造成一定影响,因此引入偏向锁概念。当一个线程频繁获得对象锁时,会在对象头中存储锁偏向的线程ID,然后当该线程再次申请或释放锁时,就不再需要做其他的同步操作,因而在一定程度上可以提高系统性能。

4.2 轻量级锁

轻量级锁在偏向锁的上一级,在偏向锁不再适用的情况下,就会向上升级。当升级为轻量级锁时,Mark Word的结构也会相应的变化。线程在栈帧中创建锁记录,接着将锁对象中Mark Word复制到线程创建的所记录中,而锁对象中的Mark Word则被替换为指向锁记录的指针,完成轻量级锁的实现。而轻量级锁的引入是为解决在重量级锁中,多线程之间的性能消耗问题。

4.3 自旋锁

自选锁顾名思义就是“自己旋转”。同样在多线程的环境下,其中一条线程获得对象锁,而其他的线程则在原地循环等待其他线程释放锁,而不是处于线程阻塞状态。这种原地循环等待的情况是会消耗CPU资源的,默认情况下循环10次。自旋锁的使用一般是小城获取锁的时间较短,让其他线程稍微等待一段时间进而再获得对象锁,比如对于同步代码块的执行一般是较快的。如果线程循环时间较长,那么操作系统便会将此线程挂起,避免资源的更多浪费。

对于自旋的概念可能不太好理解,下面写个小Demo。

public static void main(String[] args) { // 线程1 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行了..."); try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"执行完毕..."); } }).start(); // 线程2 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行了..."); try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"执行完毕..."); } }).start(); System.out.println("全部线程执行完毕..."); }

运行结果:

分析:以上案例本来的目的是当全部线程执行完毕后,再打印全部线程执行完毕。但是在多线程情况下这是无法保证的,下面进行优化。

while(Thread.activeCount() != 1){ }System.out.println("全部线程执行完毕...");

重复的代码就不再展示,只是在最后一句打印前添加死循环,让其一直判断当前活动的线程是否只剩下一个,如果是则退出while循环。那么while循环就是一直在不停循环的等待过程,直到活动线程为最后一个。

适应性自旋

是不固定自旋10次一下。它可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。

4.4 重量级锁

当轻量级锁膨胀到重量级锁之后,表示线程只能被挂起阻塞来等待被唤醒了,那么这种锁机制效率就相对比较慢,同时比较损耗系统资源。



   05 总结   


到这里关于Synchronized的总结就结束了,还有一种ReentrantLock锁也是可重入锁。


欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

JDK 13 新特性详解,看这篇就对了!

Java8 之熟透 Lambda 表达式

1 行Python代码能干哪些事,这 13个你知道吗?


关注「程序员小乐」,收看更多精彩内容
嘿,你在看吗?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存