java进阶(七)------多线程---多线程操作同一变量

时间:2021-10-18 18:09:09



情况

首先要肯定的是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