当数据在线程间被共享时,如何确保各个线程都能正确地访问它们和得到有效的数值。虽然JVM可以保证32位的数据读写是原子的(Atomic),不可分割,但存在这样的情况使我们不得不警惕:Java语言允许线程保存变量的私有专用副本(private working copy),这样各个线程就可以更高效地运行,当线程读写这些变量时,操作的实际是各自的副本,而非主存在中的正本,且只有在特定的同步点(而不是时刻),才与主内存中的正本进行一致化动作,就像我们打联网打游戏一样,大家看到的同步画面也是在若干个同步点进行了同步,嘿嘿。
比如会出现下面的情况,不同线程中都对同一个对象进行着动作:
线程一:调用变量AA的set方法,设置其值为10。
线程二:调用变量AA的set方法,设置其值为5。
线程一:调用AA的get方法,得到值5。(由于现在仍操作的是线程一的副本数据,尚未与主内存中的正本同步,这是可能出现的,因为我们不能保证它们何时肯定同步了,总有时间间隔的。)
问题就出现了,解决的办法至少有两种:
1.使用synchronized函数或代码段来访问变量AA。
2.将变量AA申明为volatile(易变的)
上述两种办法都将迫使变量与主内存中的正本一致化,不过它们还是有区别的:
――――同步(synchronized)――――――
优点:取得和释放lock时才进行与主内存正本的同步,如果有大量数据需要更新时,使用同步控制比用volatile的执行效率要高。
缺点:同步之后当然没有并发性了,各线程中的对象只能排队一个一个来调用同一方法或代码段。
――――volatile――――――――――――
优点:各线程可以并发运行。
缺点:由于被申明为volatile的变量在每次得到访问时,都会即时与主内存一致化,所以当有大量数据需要随时更新时,一致化需要一些代价。
还有一种情况,由于一般的JVM只提供了对32位数据的原子操作,所以当访问64位的long型数据时,就跨越了两个32位数据,就有可能出现当第一个线程写了前32位后,第二个线程抢先来修改了整个64位的数据,这后第一个线程再得以修改其后32位数据――唉,数据最后当然就怪了。