浅谈synchronized和volatile

前言

	本文用于初步了解synchronized和volatile,不涉及到synchronized锁优化

作用

  • synchronized:解决多个线程之间访问资源的同步性,保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行
  • volatile实现共享变量的可见性,即一个线程修改了共享变量的值后,另一个线程可以理解得到被修改后的值。此外,volatile还可以禁止JVM对指令重排序

synchronized的底层原理

synchronized实现同步的基础:Java对象的对象头中有Markword,记录锁标记位,Java每个对象都可以作为锁。
JVM基于monitor对象实现 方法同步和代码块同步。

  • 代码块同步:monitorenter 打个空格去读 指向同步代码块开始的位置,monitorexit指向结束的位置。在执行monitorenter的时候,线程试图获取monitor锁的持有权。当锁的计数器为0,成功获取,获取后锁计数器+1,执行monitorexit后锁计数器-1。
    如果获取锁失败,线程就会一直阻塞,只要其他线程释放资源。

  • 方法同步:没有用monitorenter和monitorexit指令,使用ACC_SYNCHRONIZED表示来标记该方法时一个同步方法,执行同步操作。如果标记了,执行线程将先持有monitor然后在执行方法,执行完之后释放monitor。

volatile的底层实现

volatile修饰的变量进行读写操作的时候,会多出一行Lock前缀的代码,将当前处理器的数据写回系统内存,并让其他CPU缓存了改地址的数据无效。然后处理器通过嗅探技术来保证内部它内部缓存、系统内存和其他处理器缓存的数据在总线上一致。

也可以这样理解,volatile修饰了变量,就会告诉JVM这个变量是不稳定的,访问这个变量的值要从主存中读取。

区别

  • volatile实现的是共享变量在多线程的可见性,synchronized实现的是多线程之间访问资源的同步性

  • volatile关键字是线程的轻量级实现,性能比synchronized要好。但是volatile只作用与变量,synchronized作用于代码块和方法。

  • 多线程使用volatile不会出现阻塞,而synchronized有可能发发生阻塞

  • volatile保证数据可见性,不保证数据的原子性。synchronized两者都可以保证。

什么是原子性?
一个操作或者多次操作,要么所有操作都执行,要么都不执行。不会受到任何因素的干扰而中断。

扩展

  • synchronized效率低的原因?
    synchronized基于监视器monitor实现,而monitor底层依赖操作系统的Mutex Lock实现。
    操作系统实现线程之间的切换需要从用户态切换到内核态,花费较长时间

  • synchronized关键字最重要使用的三种方式
    1.修饰实例方法:作用于当前对象的实例加锁,进入同步方法要获取当前对象实例的锁
    2.修饰静态方法:给当前类加锁,synchronized(xxx.class)
    3.修饰代码块:锁是括号里面配置的对象

  • 双重校验锁实现单例模式,线程安全吗?双重校验的原理?为什么要用volatile修饰实例?
    是线程安全的。

public class singleton{
	private volatile static singleton uniqueInstance;
	private singleton(){
	}
	public synchronized static singleton getUniqueInstance(){
		//1.先判断是否被实例化
		if(null == uniqueInstance){
			//加锁,静态方法对类对象加锁
			synchronized(singleton.class){
				if(null == uniqueInstance){
					uniqueInstance = new singleton();
				}
			}
		}
		return uniqueInstance;
	}
	
}

原理:
第一次判断null:单例模式只能创建一个实例,如果已经存在直接调用即可
第二次判断null:保证同步。当实例为null时,如果线程A进入了第一次判断(未创建实例),此时轮到线程B执行。线程B也进入了第一次判断,创建了实例。此时回到线程A执行,已经进入了第一次判断,又会再次创建一次实例。

为什么要用volatile修饰实例?
uniqueInstance = new singleton();分三部分执行的
1.为uniqueInstance分配内存地址
2.初始化uniqueInstance
3.将uniqueInstance指向分配的内存地址
但是JVM具有指令重排序的特性,执行顺序可能会变成132,在多线程环境会造成一个线程获得还没有初始化的实例。使用volatile可以禁止JVM指令重排。

都看到这里了,喜欢就点个赞吧ヽ(✿゚▽゚)ノ

猜你喜欢

转载自blog.csdn.net/glum_0111/article/details/107580903