多线程环境下,共享变量同步的方式有很多种,本文主要对synchronized
和volatile
两个修饰符作个比较,总结如下:
* volatile 仅能使用在域级别;synchronized可以使用在变量、方法、类级别。
* volatile 不执行互斥访问,不会造成线程阻塞;synchronized 会造成线程阻塞。
* volatile 修饰变量具有synchronized的可见性,但不具备其原子特性。
* volatile 本质上是告诉JVM当前变量在工作内存中的值是不确定的,需要从主存中读取;synchronized本质是当线程锁定变量时,阻止其他线程访问此变量。
使用 volatile变量提供线程安全,必须满足下面两个条件:
* 对变量的写操作不依赖于当前值。比如`i++`这种操作是不允许的。
* 对变量没有包含在具有其他变量的不变式中。
详细可以看以下例子(选自Effective Java
):
并发场景:一个线程阻止另一个线程执行任务。Java类库中提供了
Thread.shop()
方法停止其他线程,但这个方法早已不提倡使用,因为它是线程不安全的unsafe
,使用它会导致数据遭到破坏。
解决此问题,可以让第一个线程轮询poll
一个公共的boolean域,这个域初始值为false,第二个线程可以通过设置此域为true来终止第一个线程。
public class ThreadTest {
private static boolean stop;
public static void main(String[] args) {
Thread bgtr = new Thread(new Runnable() {
@Override
public void run() {
long s1 = System.currentTimeMillis();
int i = 0;
while (!stop) {
i++;
}
long s2 = System.currentTimeMillis();
System.out.println("time:" + (s2 - s1));
}
});
bgtr.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
}
}
分析这段代码,你可能认为此程序大约运行1分钟,而实验表明,大多数情况下,这个程序都永远不会终止,因为后台线程bgtr
永远在循环。
问题在于,由于没有同步,就不能保证后台线程何时“看到”主线程对
stop
变量的修改,此时JVM将这代码:
while(!done)
i++;
等价于
if(!done)
while(true)
i++;
解决此问题大部份程序员想到的是加synchronized
关键字,同步访问stop
域,代码如下:
public class ThreadTest {
private static boolean stop;
private static synchronized void requestStop() {
stop = true;
}
private static synchronized boolean stopRequested() {
return stop;
}
public static void main(String[] args) {
Thread bgtr = new Thread(new Runnable() {
@Override
public void run() {
long s1 = System.currentTimeMillis();
int i = 0;
while (!stopRequested()) {
i++;
}
long s2 = System.currentTimeMillis();
System.out.println("time:" + (s2 - s1));
}
});
bgtr.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
requestStop();
}
}
加了同步访问方法后,运行时间约为1s,达到了预期效果,但是本例的目的是为了两个线珵之间可以通信,并不是为了
互斥访问
,使用synchroinzed关键字会造成线程阻塞,性能上有一定损失。此场景下,替代的方法是使用volatile
关键字修饰变量,同步锁代码可以删除。volatile修饰符不执行互斥访问,但它可以保证任何一个纯种在读取该域时都能看到它的最新值。
public class ThreadTest {
private static volatile boolean stop;
public static void main(String[] args) {
Thread bgtr = new Thread(new Runnable() {
@Override
public void run() {
long s1 = System.currentTimeMillis();
int i = 0;
while (!stop) {
i++;
}
long s2 = System.currentTimeMillis();
System.out.println("time:" + (s2 - s1));
}
});
bgtr.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
}
}
效果等同。