线程允许多个活动页面同时执行.并发编程比单线程难,因为很多事情一起处理容易出错,也很难减少错误,但是你不能避免并发.这章帮助你编写简洁的,正确的,良好阅读性的并发编程
Item66 同步共享的可变数据
synchronized 关键字可以保证一次只有一个线程访问代码块,许多开发者认为同步就是一种互斥,防止对象在另一个线程修改时处于不一致的状态.在这种观点中,对象处于一种正确的状态,因为访问它的方法锁住了.这些方法确保对象的状态由一种状态安全的转移到另一种状态.
这种观点只正确了一半,不同步的话,一个线程的改变对其它线程是不可见的.通过相同的锁,同步不仅阻止线程在不一致状态下观察对象,而且确保每个进入同步方法或块的线程都能看到所有一致性的效果.
考虑一下从一个线程停止另一个线程,Java lib提供了Thread.stop方法,但是这个方法被遗弃了,因为它是不安全的---将导致数据损坏.一种建议方法是获取到第一个线程的boolean变量,一些人可能会这么写:
//bad
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
你可能期望这个程序运行大约一秒,然后主线程设置stopRequested为true,从而导致后台线程的循环终止 .然而在我的机器上,程序永远不会停:子线程永远在循环!
问题在于,在没有同步的情况下,无法保证后台线程何时会看到主进程所做的修改. 在没有同步的情况下,虚拟机转换成以下代码:
while (!done)
i++;
//转换
if (!done)
while (true)
i++;
修复方式如下:
//good
public class StopThread {
private static boolean stopRequested;
private static synchronized void requestStop() {
stopRequested = true;
}
private static synchronized boolean stopRequested() {
return stopRequested;
}
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested())
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
注意:读和写都是同步的,光对写方法同步,同步会失效
还可以使用volatile关键字修复为:
public class StopThread {
private static volatile boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested)
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
但使用volatile关键字要小心,考虑如下代码
//bad
private static volatile int nextSerialNumber = 0;
public static int generateSerialNumber() {
return nextSerialNumber++;
}
乍看之下没有什么问题,但是"++"操作不是原子性的,包含了两个操作,一个是读旧值,另一个是在旧值的基础上加一,在赋值.修复方式为加上synchronized关键字:
//good
private static volatile int nextSerialNumber = 0;
public static synchronized int generateSerialNumber() {
return nextSerialNumber++;
}
避免此类问题最好的方式是不要共享可变数据.要么共享不可变的数据,要么就不共享.换句话说,在一个线程中定义可变数据.如果采用此策略,则必须将其文档化,以便程序维护此原则
总之,当多个线程共享数据时,读取或写入数据的每个线程都必须执行同步.没有同步,无法保证一个线程的修改对另一个线程可见.这将会导致程序安全问题,而且很难调试.如果你只需要内部间的线程通信而不考虑互斥, volatile 关键字可以替代synchronized,但是volatile很难被正确使用
网友评论