zoukankan      html  css  js  c++  java
  • 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锁也是可重入锁。

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

    感谢阅读!

  • 相关阅读:
    IOS开发 网络发展史(NSURLProtocol)
    IOS开发 网络发展史(NSURLCach)
    IOS 开发 网络发展史(URLConnection)
    ios 网络开发(CFNetwork)
    MAC安装mysql
    conda 安装常用包
    从必应上拉取图片
    旋转数组中的二分查找
    东北大学 python模拟登录校园网(2019年6月启用新登录模式后)
    Ubuntu 安装Codeblocks
  • 原文地址:https://www.cnblogs.com/fenjyang/p/11594556.html
Copyright © 2011-2022 走看看