传说天地初开,Synchronized ,Volatile,Lock孕育而生,他们之间有着怎么样的精彩故事呢?这篇文章,辉先森与你一同看看。
他们的出现是为了解决线程安全问题。注意了,线程安全不是指线程的安全,而是指内存的安全,所以多线程的通信要保证的也是内存数据的安全。简单的说明一下原因:
在目前主流的操作系统中,每个进程都有自己的内存空间,而不能去访问其他进程的,这是有操作系统进行保证的啊,就是每个进程都有相同的逻辑空间,但是数据怎么存在真实的物理空间里面,这是有我们的操作系统去解决(分页,分段)。
线程是工作在进程之下的,而我们进程的堆内存是进程内的所以线程都可以访问到的区域。这就会造成一个问题,比如,某个线程正在辛辛苦苦的处理数据,这时他被老板叫去开会,回来发现,数据被改动过了。我们把堆内存理解成进程通信的共享内存就很容易理解了,大家都可以访问你这个内存地址,我又不知道这是不是有人在用,所以就需要我们锁的出现。
Synchronized关键字的作用:
这是一个同步锁的概念,被Synchronized修饰的代码段可防止被多个线程同时执行,必须一个线程把Synchronized修饰的代码段都执行完毕了,其他线程才能开始执行这段代码。保证了可见性,原子性,有序性。
Synchronized的工作原理:
1. 对象被创建在堆内存中的,对象在内存中的布局可分成3块区域:对象头,实例数据,对齐填充。
实例数据:存放类的属性数据信息,包括父类的属性信息。
对齐填充:这是由于JVM要求对象初始地址是8字节的整数倍。
对象头是Synchronized实现锁的基础,主要的结构是有Mark Word 和 类型指针。
Mark Word :存储对象的hashcode,锁信息或分代年龄等。
类型指针:指向对象的类元数据,JVM是通过该指针去确定对象是哪个类的实例。
2. 每一个锁都对应一个monitor对象的起始地址。每个对象实例都会有一个monitor,这是与对象一起创建销毁或者是线程获得对象锁时自动生成的。当一个monitor被某线程持有后,便处于锁定状态了。
monitor:可以理解为一个同步工具或者一种同步机制,通常被描述为一个对象。monitor是线程私有的数据结构,每一个线程都有一个可用的monitor record列表和一个全局可用列表。每一个锁住的对象都会和一个monitor关联,同时monitor有一个owner字段存放拥有该锁的线程唯一标识,表示该锁被这个线程占用了。
3. Synchronized常被用于修饰代码块或者方法,我们看看这个两者有什么区别。
Synchronized修饰代码块:底层是使用了monitorenter 和 monitorexit 指令。 当执行monitorenter指令时,会尝试获得monitor对象,成功就把锁的计数器_count加1,当执行到 monitorexit 指令时,计数器就减1。
这里虽然没有贴源码,但是读者看源码是可以知道是会出现两个monitorexit 指令,这可能会让我们有点困惑,讲道理monitorenter 和 monitorexit 指令应该是一同出现的吧,那为什么会出现两个monitorexit 指令呢?
其实啊,这是为了保证在方法异常时,monitorenter 和 monitorexit 指令也是可以配对执行,编译器会自动产生一个异常处理器,目的是用来执行异常的monitorexit 指令去释放monitor。
Synchronized修饰方法:是用ACC_SYNCHRONIZED标识,JVM会通过该标识去执行上面的同步过程二、Synchronized的优化
在JDK1.5之前Synchronized的效率是很低的,这是因为底层的monitorenter 和 monitorexit 指令都是运行在内核态的,而用户态去到内核态的时间开销是不低的。以至于在JDK1.5,引入了Synchronized的新概念:无锁,偏向锁,轻量级锁,重量级锁。这些只是使用Synchronized锁后的一种锁状态,存储的地方就是开头介绍的Mark Word中。
这里先不讲这四种状态的介绍,我们留到之后在来开一片JAVA锁的全家桶。现在我们只需要先知道,锁的状态的等级:无锁<偏向锁<轻量级锁<重量级锁。锁的状态只能从低等级的升级到高等级,而不能反向,也就是说升级的方向是:无锁->偏向锁->轻量级锁->重量级锁。
优化的重点来了,前面搞这么多的锁状态其实就是要优化解决Synchronized频繁的从用户态到内核态的转移。真正会进入到内核态的是升级到重量级锁时。 这也是现在Synchronized的性能可以和Lock交手的原因。
volatile关键字的作用:
保证可见性和有序性(不保证原子性),如果一个变量被volatile修饰我们称为共享变量,那一个线程修改了这个共享变量后,其他线程是立马可以知道的。因为该变量会强制立刻刷新到主存。
禁止指令重新排序,在volatile变量之前的指令不能在volatile变量之后执行,在volatile变量之后的指令不能在volatile变量之前执行。(使用场景可以看看单例模式的双重校验锁机制)。
volatile和Synchronized 的区别:
(1)volatile只能作用与变量,使用范围较小。Synchronized 可以用在变量,方法,类,同步代码块等,使用广。
(2)volatile保证可见性和有序性(不保证原子性),而Synchronized都保证。
(3)volatile不会造成线程阻塞,Synchronized可能会造成线程阻塞。
Lock关键字的作用:
Lock和Synchronized差不多,只是一个是有程序员手动去释放锁(Lock),一个是由JVM去自动释放锁(Synchronized)。
Lock和Synchronized 的区别:
(1)用法不同:Synchronized是托管于JVM会自动的释放锁,而Lock需要我们显示的指定头尾。
(2)性能:在JDK1.5之后两者差不多了,可能还是lock会快一点点吧。
(3)底层不同:Synchronized是悲观锁,最终基于操作系统的mutex lock来实现互斥;Lock是乐观锁,CAS+volatile。
(4)功能:Lock提供高级功能实现:可定时,可轮询,可中断,读写锁,公平锁或者非公平锁。而Synchronized只是一个普通的非公平锁。但是他们两者都是可重入锁。
编程世界博大精深,需要我们细细品味,好好学习,共同进步,加油少年郎。对了,差点忘记怎么暴打面试官了,当你可以理解这篇文章了,被问到Synchronized时,嘻嘻,就是在送。