情况
首先要肯定的是ThreadLocal和局部变量是线程安全的,静态和实例变量都是不安全的。
我们常常在系统中会用一些 静态变量 作为 共同的状态标记。
但在多线程中常常发现这个 变量的增减 会出现错乱 并不是预期中的结果显示。
例如:
package test.autorun; public class ShareVar { private static int nCount=0; public int getnCount() { return nCount; } public void setnCount(int nCount) { ShareVar.nCount = nCount; } }
原因
内存机制中的 "副本"概念
多个线程访问一个成员变量时 每个线程都会得到一个该变量的副本 在自己的线程的栈中保存、计算 以提高速度。 但是这样就会有同步的问题了。 当一个线程修改了自己栈内副本的值 还没有立即将同步到主存中, 其他线程再来获取主存中的该变量时 就会得到过期数据。
解决办法
为了解决这种问题 可以使用synchronized对该变量的操作同步 , 或使用volatile关键字声明该变量为易变对象 这样的话 每个线程就不会创建副本到自己的栈中 而是直接操作主存。
volatile
在对象/变量前加上 volatile 。 Volatile修饰的 成员变量 在每次被 线程 访问时,都强迫从 共享内存 中重读该成员变量的值。而且,当 成员变量 发生变化时,强迫线程将变化值回写到 共享内存 。这样在任何时刻,两个不同的线程总是看到某个 成员变量 的同一个值。 Java语言 规范中指出:为了获得最佳速度,允许线程保存共享 成员变量 的私有拷贝,而且只当线程进入或者离开 同步代码块 时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享 成员变量 的变化。而volatile 关键字 就是提示JVM:对于这个 成员变量 不能保存它的私有拷贝,而应直接与共享成员变量交互。使用建议:在两个或者更多的线程访问的 成员变量 上使用volatile。当要访问的 变量 已在synchronized代码块中,或者为 常量 时,不必使用。由于使用volatile屏蔽掉了JVM中必要的 代码优化 ,所以在效率上比较低,因此一定在必要时才使用此 关键字 。package test.autorun; public class ShareVar { private volatile static int nCount=0; public int getnCount() { return nCount; } public void setnCount(int nCount) { ShareVar.nCount = nCount; } }
synchronized
将对象/变量加上锁 synchronized 修饰。在线程中,使用同步方法或者同步块。
package test.autorun; public class ShareVar { private static int nCount=0; public int getnCount() { return nCount; } public synchronized void setnCount(int nCount) { ShareVar.nCount = nCount; } }
TimerTask
使用带有线程安全的线程。如:继承 TimerTask 类实现线程,用 Timer.schedule 启动线程 。
选择
但synchronized同步方法和块的话 损耗资源和性能是较大的,它会在该方法和块中阻塞变成类似于单线程来运作,所有优先考虑volatile