Effective Java读书笔记(九)
来源:互联网 发布:免费的网络验证 编辑:程序博客网 时间:2024/05/16 12:29
同步访问共享的可变数据
// Broken! - How long would you expect this program to run?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的值所做的改变。没有同步,虚拟机将这个代码:
while(!done) i++;
转变成这样:
if(!done) while(true) i++;
修正这个问题的一种方式是同步访问stopRequested域:
// Properly synchronized cooperative thread terminationpublic 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(); }}
另外一种更高效,更优雅的方法是将stopRequested声明为volatile,可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值。
// Cooperative thread termination with a volatile fieldpublic 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; }}
//Broken - requires synchronization!private static volatile int nextSerialNumber = 0;public static int generateSerialNumber() { return nextSerialNumber++;}
由于增量操作符(++)不是原子的,这个操作会导致安全性失败。修复的第一种方法是在方法的生命中增加synchronized修饰符,这个时候就可以并且应该去掉volatile修饰符。第二种方法是使用AtomicLong类型:
private static final AtomicLong nextSerialNum = new AtomicLong();public static long generateSerialNumber() { return nextSerialNum.getAndIncrement();}
简而言之,当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。
避免过度同步
为了避免活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制。在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客户端以函数对象的形式提供的方法。
// Broken - invokes alien method from synchronized block!public class ObservableSet<E> extends ForwardingSet<E> { public ObservableSet(Set<E> set) { super(set); } private final List<SetObserver<E>> observers = new ArrayList<SetObserver<E>>(); public void addObserver(SetObserver<E> observer){ synchronized(observers) { observers.add(observer); } } public boolean removeObserver(SetObserver<E> observer) { synchronized(observers) { return observers.remove(observer); } } private void notifyElementsAdded(E element) { synchronized(observers) { for(SetObserver<E> observer : observers) observer.added(this, element); } } @Override public boolean add(E element) { boolean add = super.add(element); if(added) notifyElementsAdded(element); return added; } @Override public boolean addAll(Collection<? extends E> c) { boolean result = false; for(E element : c) result |= add(element); return result; }}public interface SetObserver<E> { // Invoked when an element is added to the ovservable set void added(ObservableSet<E> set, E element);}
下面的调用会出现问题:
set.addObserver(new SetObserver<Integer>() { public void added(ObservableSet<Integer> s, Integer e){ System.out.println(e); if(e == 23) s.removeObserver(this); }})
因为在遍历的过程中对列表进行了修改。
可通过将外类方法的调用移除同步的代码块来解决这个问题。
// Alien method moved outside of synchronized block - open callsprivate void notifyElementAdded(E element) { List<SetObserver<E>> snapshot = null; Synchronized(observers) { snapshot = new ArrayList<SetObserver<E>>(observers); } for(SetObserver<E> observer : snapshot) observer.added(this, element);}
还有一个更好的方法,就是使用Java类库提供的并发集合。
// Thread-safe observable set with CopyOnWriteArrayListprivate final List<SetObserver<E>> observers = new CopyOnWriteArrayList<SetObserver<E>>();public void addObserver(SetObserver<E> observer) { observers.add(observer);}public boolean removeObserver(SetObserver<E> observer){ return observers.remove(observer);}private void notifyElementsAdded(E element) { for(SetObserver<E> observer : observers) observer.added(this, element);}
通常,你应该在同步区域内做尽可能少的工作。获得锁,检查共享数据,根据需要转换数据,然后放掉锁。
executor和task优于线程
ExecutorService executor = Executors.newSingleThreadExecutor();executor.execute(runnable);executor.shutdown();
并发工具优先于wait和notify
线程安全性的文档化
慎用延迟初始化
不要依赖于线程调度器
避免使用线程组
关于java.util.concurrent包下的类,可参考java.util.concurrent
0 0
- Effective Java读书笔记(九)
- Effective Java 读书笔记(九):并发
- Effective Java读书笔记九(Java Tips.Day.9)
- Effective Java 读书笔记(一)
- Effective Java 读书笔记(二)
- Effective Java读书笔记(一)
- Effective Java读书笔记(一)
- Effective Java读书笔记(二)
- Effective Java读书笔记(三)
- Effective Java读书笔记(四)
- effective java读书笔记 (二)
- Effective Java读书笔记(五)
- effective java 读书笔记(三)
- effective java 读书笔记(四)
- effective java 读书笔记(五)
- Effective Java读书笔记(六)
- Effective Java读书笔记(七)
- Effective Java读书笔记(八)
- Java程序员应该掌握哪些东西?
- 初学Redis——用Redis作为Mysql数据库的缓存 (3)
- iOS绘图教程
- iOS开发里的线程安全机制
- 最少编码原则
- Effective Java读书笔记(九)
- 安装mongodb
- 文章标题
- 209. Minimum Size Subarray Sum
- KVO 和 KVC
- mysql表锁与事务等问题
- 10种简单的Java性能优化
- Quartz.NET 入门
- JAVA将字符串变为输入流