多线程的安全问题
一、可见性
- 例如下面的程序,先启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值。
//1.线程类 public class MyThread extends Thread { public static int a = 0; @Override public void run() { System.out.println("线程启动,休息2秒..."); try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("将a的值改为1"); a = 1; System.out.println("线程结束..."); } }
//测试类public class Demo {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
//主线程继续
while (true) {
if (MyThread.a == 1) {
System.out.println("主线程读到了a = 1");
}
}
}
}
二、有序性
- 有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:
int a = 10; //1
int b = 20; //2
int c = a + b; //3
第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会
将1,2编译完毕。1和2先编译谁,不影响第三行的结果。
- 但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响:
三、原子性
- 请看以下示例
//线程类 public class MyThread extends Thread { public static int a = 0; @Override public void run() { for (int i = 0; i < 10000; i++) { a++; } System.out.println("修改完毕!"); } } //测试类 public class Demo { public static void main(String[] args) throws InterruptedException { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); Thread.sleep(1000); //总是不准确的。原因:两个 线程访问a的步骤不具有:原子性 System.out.println("获取a最终值:" + MyThread.a); } }
- 内存工作图
线程t1先读取a 的值为:0
t1被暂停
线程t2读取a的值为:0
t2将a = 1
t2将a写回主内存
t1将a = 1
t1将a写回主内存(将t2更改的1,又更改为1)
所以两次加1,但结果仍为1,少加了一次。
原因:两个线程访问同一个变量a的代码不具有"原子性"
四、解决
volatile关键字----解决可见性和有序性
原子类、synchronized----解决原子性、有序性、可见性