为什么要使用synchronized 关键字,这个有什么用?
在多线程中,首先得存在共享数据(也称临界资源),其次还得存在多条线程共同操作共享数据。存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个的名称叫互斥锁,独占锁/排它锁都是这个意思,这样主要就是保证数据的安全性问题。
在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代volatile功能)。
多线程的三个特性:原子性,可见性,有序性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。 可见性:是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。显然,对于串行来说是不存在的。 有序性:在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。
synchronized 和 volatile
synchronized
关键字,它会阻止其它线程获取当前对象的监控锁(管程moniter),这样就使得当前对象中被synchronized
关键字保护的代码块无法被其它线程访问,保证了线程安全,是一个JVM层面的锁,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作。
volatile会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同样这个内存屏障也会禁止指令重排序 。编译出来的汇编代码会有一行 lock 0x0 addl (esp 0 ),这个lock的所有就是一个内存屏障,esp寄存器的操作为0,做了一个空的操作,然后这个操作会在工作内存中store/wirte写入到主存中,根据CPU的MESI协议,从而会导致各个工作内存的读取的变量会缓存失效,从而要读取主存,这就是如何保证的可见性,详细可以参考《深入理解JVM虚拟机>
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatitle只保证线程可见性(刷新线程可见性),有序性(禁止指令重排序),但是不保证原子性,也就是说不能保证线程安全
那么这里面有几个重要概念 happens-before ,指令重排序,以及JMM内存模型
这里这几个概念先不展开讨论了,各位读者可以自行搜索 https://www.cnblogs.com/Scramblecode/p/11392639.html
------------------------------------------synchronized 核心原理解析------------------------------------------------------------------------- synchronized 通过以上了解,我们知道synchronized锁住的是对象(当前对象),这里也有一些同学会反驳说synchronized还可以锁类,static修饰的方法,这也不是对象啊,注意下,我这里说的对象,可以理解为锁住的属性在jvm运行时数据内存的一个区域和地址。
在使用synchronized使用代码快的反编译javap 字节码的时候会看到两个指令monitorenter 和 monitorexit 这两个指令分别对应一个管程(监视器)的进入和出去,因为Java的跨平台性是因为Jvm来实现的,对于不同的操作系统而言,也会有不同的操作指令,jvm要保证兼容跨平台性,所以只有对于操作系统底层的一些汇编指令再次进行封装成jvm统一的字节码执行(对操作系统字节再次进行了一次封装)所以,synchronized其实是使用的moniter来保证安全性,moniter管程(监视器)是jvm中实现的。而moniter中依赖于一个叫 ObjectMonitor 模式的实现 这是 JVM 内部基于 C++ 实现的一套机制,基本原理如下所示:
这里笔者也看了下jvm的源码 找到了 ObjectMonitor写过的源码
当一个线程需要获取 Object 的锁时,会被放入 EntryList 中进行等待,如果该线程获取到了锁,成为当前锁的 owner。如果根据程序逻辑,一个已经获得了锁的线程缺少某些外部条件,而无法继续进行下去(例如生产者发现队列已满或者消费者发现队列为空),那么该线程可以通过调用 wait 方法将锁释放,进入 wait set 中阻塞进行等待,其它线程在这个时候有机会获得锁,去干其它的事情,从而使得之前不成立的外部条件成立,这样先前被阻塞的线程就可以重新进入 EntrySet 去竞争锁。这个外部条件在 monitor 机制中称为条件变量,这也就是什么wait() notify() 等等 这些方法要定义到Object里面,而且这些方法调用需要在synchronized同步代码块中,参考《深入理解jvm虚拟机》
下面解释一下synchronized的锁升级过程 一般情况下只能升级不能降级 在JDK1.6中,Java对synchronized锁做了一些列的优化引入了偏向锁,轻量级锁 一般情况下来说,无锁--->偏向锁-(jdk1.6默认开启)-->轻量级锁--->重量级锁