本文介绍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() );
TimeUnit
.SECONDS
.sleep(2);
stopFlag
= true;
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() );
TimeUnit
.SECONDS
.sleep(2);
setStopFlag();
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() );
TimeUnit
.SECONDS
.sleep(2);
stopFlag
= true;
TimeUnit
.SECONDS
.sleep(2);
System
.out
.println( backgroundThred
.getState() );
}
}
实际运行结果:
第三种方法,是避免分享可变数据,将可变数据限制在单个线程中。即可避免该问题。