本文介绍Java多线程访问共享的数据的三种策略
- 第一种:使用synchronized同步方法共享可变数据
- 第二种:使用volatile共享可变数据
- 第三种:不共享可变数据,只共享不可变数据
首先看一段错误的代码
在下例中,main方法为主线程,backgroundThred为子线程。在主线程中,我们首先调用了子线程,并输出状态,此时应该为RUNNABLE(执行中)。为了使子线程能够允许2秒我们调用了sleep()方法来睡眠2秒,2秒后将stopFlag置为true。这时我们预期,子线程的while方法应该停止,所以再次睡眠两秒后,输出的backgroundThred状态应该为TERMINATED(终止)。
※插播Thread的六种状态
NEW:未启动的线程。
RUNNABLE:在JVM上执行的状态。
BLOCKED:阻塞状态,等待锁释放的状态。
WAITING:无期限等待其它线程的状态。
TIMED_WAITING:等待固定的停止时间或是等待其它线程实行的状态。
TERMINATED:执行结束状态。
package test;
import java.util.concurrent.TimeUnit;
public class Test {
private static boolean stopFlag = false;
public static void main(String[] args) throws InterruptedException {
// 生成一个子线程
Thread backgroundThred = new Thread( new Runnable() {
public void run() {
int i = 0;
// 判断停止符号是否为真
while( !stopFlag ){
i++;
}
}
});
backgroundThred.start();
System.out.println( backgroundThred.getState() );
// 使主线程停止2秒,为了子线程运行2秒
TimeUnit.SECONDS.sleep(2);
// 设置线程停止的flag为true
stopFlag = true;
// 使主线程停止2秒,为了确认子线程有没有终止
TimeUnit.SECONDS.sleep(2);
System.out.println( backgroundThred.getState() );
}
}
但实际执行结果,子线程并未被终止。程序无法前进。因为主线程和子线程的stopFlag并未被同步。
-
使用第一种方法来修正这个问题。synchronized同步方法。
不在主线程中执行stopFlag = true;而是创建用synchronized修饰的stopFlag的读写方法(getter和setter方法)。并在主程序中调用setter方法。
package test;
import java.util.concurrent.TimeUnit;
public class Test {
private static boolean stopFlag = false;
private static synchronized void setStopFlag() {
stopFlag = true;
}
private static synchronized boolean getStopFlag() {
return stopFlag;
}
public static void main(String[] args) throws InterruptedException {
// 生成一个子线程
Thread backgroundThred = new Thread( new Runnable() {
public void run() {
int i = 0;
// 判断停止符号是否为真
while( !getStopFlag() ){
i++;
}
}
});
backgroundThred.start();
System.out.println( backgroundThred.getState() );
// 使主线程停止2秒,使子线程运行2秒
TimeUnit.SECONDS.sleep(2);
// 线程停止的flag
setStopFlag();
// 使主线程停止2秒,为了确认子线程有没有终止
TimeUnit.SECONDS.sleep(2);
System.out.println( backgroundThred.getState() );
}
}
此时的实行结果,和预期一样,在调用setStopFlag()方法后,设置了stopFlag为真,因为有synchronized修饰,它的值将能在线程间通信。
实际运行结果:
-
使用第二种方法来修正问题,volatile修饰符。使用volatile修饰符修饰的变量可以保证任何一个线程 在读取该域时都能看到最近刚刚被写入的值。
使用这种方法修改,只需在错误的代码中的stopFlag变量添加volatile修饰符即可。
package test;
import java.util.concurrent.TimeUnit;
public class Test {
private static volatile boolean stopFlag = false;
public static void main(String[] args) throws InterruptedException {
// 生成一个子线程
Thread backgroundThred = new Thread( new Runnable() {
public void run() {
int i = 0;
// 判断停止符号是否为真
while( !stopFlag ){
i++;
}
}
});
backgroundThred.start();
System.out.println( backgroundThred.getState() );
// 使主线程停止2秒,为了子线程运行2秒
TimeUnit.SECONDS.sleep(2);
// 设置线程停止的flag为true
stopFlag = true;
// 使主线程停止2秒,为了确认子线程有没有终止
TimeUnit.SECONDS.sleep(2);
System.out.println( backgroundThred.getState() );
}
}
实际运行结果: