synchronized是Java中实现锁的一种方式,咱们能够经过synchronized来给一个方法,一个属性,一个对象等资源进行加锁。html
可能你会说,是由于当某个资源被多个线程访问时,咱们须要同步协调线程访问的顺序,在这种状况下,咱们要对该资源加锁。java
好比,在火车票放票期间,禁止售票员访问票源,这个本质上就是将资源(火车票源)加锁,协调了售票员和管理员的操做顺序。程序员
若是给外行人解释,这么说已经足够了。但对于一个有态度的技术人来讲,这种描述就太浅显了。这个问题,咱们还得从源头上提及。安全
咱们使用一段代码来表达上面的例子:多线程
//火车票程序
public class TrainTicket {
int beiJing = 0, shangHai = 0;
//放票
public void writer() {
beiJing = 1;
shangHai = 2;
}
//查票
public void reader() {
int r1 = beiJing;
int r2 = shangHai;
}
}
复制代码
//T-1 放票线程
recordering.writer();
复制代码
//T-2 查票线程
recordering.reader();
复制代码
咱们按“顺序”执行T1和T2,结果会是什么呢? 咱们指望的结果r1=1, r2=2 可是结果极可能是r1=0, r2=0 也多是r1=1, r2=0 也多是...oracle
为何和咱们预期的结果不同呢,是哪里出了问题?app
这就是咱们今天要重点说的一个概念——重排序优化
重排序(Reordering)是编译器(Compiler)为了优化执行效率而作的一种策略。在单线程中,重排序要保证不影响程序的语义,所以对于没有依赖关系的语句,均可能被重排序。 好比spa
int a=0;
int b=1;
复制代码
第一行语句和第二行语句并不构成依赖,因此编译器能够任意调换顺序。线程
重点来了,那么在多线程中环境中,涉及到重排序时,就会遇到线程安全的问题。 所以,Java编译器并不会保证线程安全,线程是否安全由程序员确保的。
这不是甩锅嘛!!!
么办法,这锅就是程序员的!
好吧,让咱们再回到最初的问题: 怎么更好地背锅?
哦,不对。
为何,咱们须要对共享资源加锁?
敲黑板,划重点
加锁是为了消除程序因重排序而产生的线程安全问题,最终保证语义的一致性和数据的一致性!
说到这,好像说的比较清楚了,可是还有一个根本性的问题
在Java的内存模型(JMM)中定义了一系列的happen-before原则,具体这个原则如何描述,笔者也很差把握,若是执意要下定义的话,我认为: happen-before是Java提供的一系列的确保局部有序的规则。 再具体一点就是,若是A操做happens-before于B操做,那么也就意味着A的操做结果对B是可见的。
能够回到咱们火车票的例子理解一下,若是出票操做happens-before于查票操做,那么出票的结果对查票来讲必定是可见的,也就是说出票结果必定会被正确查到。
下面是具体的每一条规则
这些规则的中文翻译网上有不少,我之因此贴英文,主要是考虑到反正这种条文没有人会去记,反却是贴英文官方文档更合适一些,也能帮助到想查官方文档的同窗。
针对第二条(关于锁)的规则扩展一下。 同一个锁的unlock操做在lock以前,也就是说 一个锁处于被锁定状态,那么必须先执行unlock操做后面才能进行lock操做。
正式由于有了这条规则,咱们就能够经过加锁的方式实现线程安全,将以上代码改造一下
//火车票程序
public class TrainTicket {
int beiJing = 0, shangHai = 0;
//放票
public synchronized void writer() {
beiJing = 1;
shangHai = 2;
}
//查票
public synchronized void reader() {
int r1 = beiJing;
int r2 = shangHai;
}
}
复制代码
这样,将两个方法都加上锁,这样就实现了线程的安全。
synchronized代码块编译以后会生成一个monitorenter指令和一个或多个monitorexit指令,大体以下:
monitorenter
/*---------*/
code
/*--------*/
monitorexit
复制代码
对于monitorenter和monitorexit,咱们能够理解为每一个锁对象拥有一个锁的计数器和一个指向持有该锁的线程的指针。
当执行monitorenter指令时,若是目标锁对象的计数器为0,那么说明它没有被别的线程持有,在这个状况下,Java虚拟机会将该锁对象的持有线程设置为当前线程,而且将计数器加1.
在目标锁对象的计数器不等于0的状况下,若是其它线程访问,则须要等待,直到持有锁的线程释放该锁。(联想一下happen-before中的第二条规则)
有几个问题是可扩展的
一、重排序有三个维度上的,分别是编译器,内存和处理器。本文中只提到了编译器的重排序,而没有提到内存的重排序。
二、从内存的维度上,是如何禁止重排序的?
三、happen-before原则中提到了volatile类型变量,这个类型的变量有什么特殊之处,咱们能用它解决什么呢?
下次有时间再细说
本文原创,转载请注明出处
引用参考(reference):