Synchronized实现原理

时间:2019-09-26
本文章向大家介绍Synchronized实现原理,主要包括Synchronized实现原理使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一,前言

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

1.1,Synchronized 作用

  • 确保线程互斥的访问同步代码
  • 保证共享变量的修改能够及时可见
  • 有效解决重排序问题

二,从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是可重入锁。

三,可重入锁

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

​ 请看实例代码:

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便会产生死锁,另外两个方法的线程名称是相同的,也可以证明该线程拿到的就是同一个对象锁。

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

四,锁的优化

​ 在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,重量级锁

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

五,总结

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

​ 以上内容均是学习总结,如有不适之处欢迎留言指正。

感谢阅读!

原文地址:https://www.cnblogs.com/fenjyang/p/11594556.html