Java中,synchronized和lock是用来处理并发过程中,线程之间互斥关系,即被synchronized和lock修饰的变量或方法或代码段能保证同一时间有且只有一个线程在处理,这样可以保证其它线程能够处理最新的数据,而且数据在中途不会被其它线程所修改。此外,还有一个功能就是保证数据修改的可见性。
可见性表示一个线程在修改一个数据时,对于其它线程是可见的,即其它线程是会知道这个数据在被修改了,从而会等数据修改完后再取值,保证取到的值是最新的。
首先,要了解一下内存。目前内存包括缓存和内存,由于CPU的计算速度快于读取内存数据的速度,所以为了缩短读写时间,在原有的内存的基础上,又加了缓存(买电脑的时候经常会看到一级缓存,二级缓存,三级缓存,缓存越大,价钱越高),CPU会把最常用的数据从内存加载到缓存中来提高数据的读写速度(CPU还有自己的寄存器,该读写速度最快,容量最小)。在Java中,每个线程都有自己的一个working memory(工作存储),该空间就是放在寄存器和缓存中。当一个线程在修改一个变量时,会检查自己的工作存储中是否存在该变量,如果存在就会直接修改,如果不存在,就会从内存中加载该变量后再修改,然后将更新后的变量再写回内存中。这个时候,如果同时存在A和B两个线程在修改同一个变量,就会出现问题(比如A修改的数据被B修改的数据所覆盖等)。所以为了保证数据的正确性,B线程需要知道A在修改变量(即A修改的动作对于B是可见的),等A修改完后写回内存后,在重新加载变量再修改。
其次要了解一个happen-before(是偏序关系)的关系,表示actionA happen-before actionB。所有线程的工作在内存中都是一个action,java的内存中存储了所有线程工作的action,即指令集。在Java中,一个变量如果被修饰为volatile,那么对于该变量的操作(指令)都要遵循happen-before原则,即actionB需要在actionA完成后才能开始执行。在多线程中,一个线程要读一个变量值的action,需要等待其它所有线程对该变量写acitons后才能执行。所以可见性的实现方法就是对相关的指令进行的排序,保证数据的一致性。Java的内存模型中,定义了happen-before的规则:
- 程序顺序规则:一个线程中的每个操作,happens-before于随后该线程中的任意后续操作
- 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的获取
- volatile变量规则:对一个volatile域的写,happens-before于对这个变量的读
- 传递性:如果A happens-before B,B happens-before C,那么A happens-before C
- start规则:如果线程A执行线程B的start方法,那么线程A的ThreadB.start()happens-before于线程B的任意操作
- join规则:如果线程A执行线程B的join方法,那么线程B的任意操作happens-before于线程A从TreadB.join()方法成功返回。