可见性,原子性与有序性
原子性 原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不 会被其他线程影响
X=10;?//原子性(简单的读取、将数字赋值给变量) Y?=?x;?//变量之间的相互赋值,不是原子操作 X++;?//对变量进行计算操作?不是原子操作 X?=?x+1;
可见性 可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。对于串行程序来说,可见性是不存在的,因为我们在任何一个操作中修改了某个变量的值,后续的操作中都能读取这个变量值,并且是修改过的新值。但在多线程环境中可就不一定了,由于线程对共享变量的操作都是线程拷贝到各自的工作内存进行操作后才写回到主内存中的,这就可能存在一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步 延迟现象就造成了可见性问题
有序性
在本线程内,代码的执行是按顺序依次执行的,所有操作都视为有序行为,如果是多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的。
如何解决原子性&可见性&有序性问题
原子性问题: 除了JVM自身提供的对基本数据类型读写操作的原子性外,可以通过?synchronized和 Lock实现原子性。因为synchronized和Lock能够保证任一时刻只有一个线程访问该代码 块。
可见性问题: volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值立即 被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中 读取新值。synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个 线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。
有序性问题: 在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
volatile关键字
volatile是Java虚拟机提供的轻量级的同步机制。volatile关键字有如下两个作用保证被volatile修饰的共享变量对所有线程总数可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值,新值总是可以被其他线程立即得知。 关于volatile的可见性作用,我们必须意识到被volatile修饰的变量对所有线程总数立即 可见的,对volatile变量的所有写操作总是能立刻反应到其他线程中
volatile无法保证原子性: 示例 public?class?VolatileTest { ? ? ?public?static?volatile?int?count?=0; ? ? ?public?static?void?increase(){ ? ? ? count++; ? ? ?} ?} 在并发场景下,count变量的任何改变都会立马反应到其他线程中,但是多条线程同时调用increase()方法的话,就会出现线程安全问题,毕竟i++;操作并不具备原子性,该操作假如有A B线程?当线程A和B同时去主内存读取count到工作内存 此时A线程已经做完了+1操作 修改了工作内存的数据 而B线程也将count已经在寄存器做了+1操作 虽然A线程及时通知了B线程 ?B线程也清空了工作内存中的count 但是B线程还是会将count+1后的数据写入到工作内存 然后线程A B都会将修改后的数据写会到主内存 造成数据的一个覆盖。这也就造成了线程安全失败,因此对increase方法必须使用synchronized修饰,以便保证线程安全,需要注意的是一旦使用synchronized修饰方法后,由于synchronized本身也具备与volatile相同的特性,即可见性,因此在这样种情况下就完全可以省去volatile修饰变量。
用volatile去修饰变量 那么在汇编层面在变量前面加一个lock前缀 而lock前缀又会触发缓存一致性协议 通过缓存一致性协议可以保持即使多个线程在操作是也能保证可见性。
|