Effective Java 67 Avoid excessive synchronization
Principle
- To avoid liveness and safety failures, never cede control to the client within a synchronized method or block.
- Do as little work as possible inside synchronized regions.
- You should make a mutable class thread-safe (Item 70) if it is intended for concurrent use and you can achieve significantly higher concurrency by synchronizing internally than you could by locking the entire object externally. Otherwise, don't synchronize internally. Let the client synchronize externally where it is appropriate. (eg. StringBuffer vs. StringBuilder)
- If a method modifies a static field, you must synchronize access to this field, even if the method is typically used only by a single thread.
The failure by invoking alien method
/**
* Demo for "67 Avoid excessive synchronization".
*/
package com.effectivejava.concurrency;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.effectivejava.classinterface.ForwardingSet;
/**
* @author Kaibo Hao
*
*/
public class ObservableSet<E> extends ForwardingSet<E> {
/**
* @param s
*/
public ObservableSet(Set<E> s) {
super(s);
}
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 notifyElementAdded(E element) {
synchronized (observers) {
for (SetObserver<E> observer : observers)
// calling the alien method
observer.added(this, element);
}
}
@Override
public boolean add(E element) {
boolean added = super.add(element);
if (added)
notifyElementAdded(element);
return added;
}
@Override
public boolean addAll(Collection<? extends E> c) {
boolean result = false;
for (E element : c)
result |= add(element); // calls notifyElementAdded
return result;
}
public static void main(String[] args) {
ObservableSet<Integer> set =
new ObservableSet<Integer>(new HashSet<Integer>());
set.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
}
});
for (int i = 0; i < 100; i++)
set.add(i);
}
}
Case 1: Failed to remove an element from a list in the midst of iterating over it, which is illegal.
Root Cause - The iteration in the notifyElementAdded method is in a synchronized block to prevent concurrent modification, but it doesn't prevent the iterating thread itself from calling back into the observable set and modifying its observers list.
// Removing the Observer during the iteration.
set.addObserver(new SetObserver<Integer>() {
public void added(ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) s.removeObserver(this);
}
});
Case 2: Failed to background thread for removing.
Root Cause: - The object in the synchronized region are locked by the main thread which cannot be modified by the background thread.
// Observer that uses a background thread needlessly
set.addObserver(new SetObserver<Integer>() {
@Override
public void added(final ObservableSet<Integer> s, Integer e) {
System.out.println(e);
if (e == 23) {
ExecutorService executor = Executors
.newSingleThreadExecutor();
final SetObserver<Integer> observer = this;
try {
executor.submit(new Runnable() {
@Override
public void run() {
s.removeObserver(observer);
}
}).get();
} catch (ExecutionException ex) {
throw new AssertionError(ex.getCause());
} catch (InterruptedException ex) {
throw new AssertionError(ex.getCause());
} finally {
executor.shutdown();
}
}
}
});
Reentrant lock : Locks in Java programming language are reentrant, in other words such calls above won't deadlock.
Solution 1 - Taking snapshot and move Alien method outside of synchronized block - open calls
private 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);
}
Solution 2(Prefered) - Thread-safe observable set with CopyOnWriteArrayList
// Thread-safe observable set with CopyOnWriteArrayList
private 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 notifyElementAdded(E element) {
for (SetObserver<E> observer : observers)
observer.added(this, element);
}
CopyOnWriteArrayList
It is a variant of ArrayList in which all write operations are implemented by making a fresh copy of the entire underlying array. Performance may be atrocious, but it's perfect for observer lists which are rarely modified and often traversed.
Note
In a multicore world, the real cost of excessive synchronization is not the CPU time spent obtaining locks; it is the lost opportunities for parallelism and the delays imposed by the need to ensure that every core has a consistent view of memory.
If you do synchronize your class internally, you can use various techniques to achieve high concurrency, such as lock splitting, lock striping, and nonblocking concurrency control.
Summary
To avoid deadlock and data corruption, never call an alien method from within a synchronized region. More generally, try to limit the amount of work that you do from within synchronized regions. When you are designing a mutable class, think about whether it should do its own synchronization. In the modern multicore era, it is more important than ever not to synchronize excessively. Synchronize your class internally only if there is a good reason to do so, and document your decision clearly (Item 70).
Effective Java 67 Avoid excessive synchronization的更多相关文章
- Effective Java 07 Avoid finallizers
NOTE Never do anything time-critical in a finalizer. Never depend on a finalizer to update critical ...
- Effective Java 50 Avoid strings where other types are more appropriate
Principle Strings are poor substitutes for other value types. Such as int, float or BigInteger. Stri ...
- Effective Java 59 Avoid unnecessary use of checked exceptions
The burden is justified if the exceptional condition cannot be prevented by proper use of the API an ...
- Effective Java 73 Avoid thread groups
Thread groups were originally envisioned as a mechanism for isolating applets for security purposes. ...
- Effective Java 05 Avoid creating unnecessary objects
String s = new String("stringette"); // Don't do this. This will create an object each tim ...
- Effective Java 48 Avoid float and double if exact answers are required
Reason The float and double types are particularly ill-suited for monetary calculations because it i ...
- Effective Java Index
Hi guys, I am happy to tell you that I am moving to the open source world. And Java is the 1st langu ...
- 《Effective Java》读书笔记 - 10.并发
Chapter 10 Concurrency Item 66: Synchronize access to shared mutable data synchronized这个关键字不仅保证了同步,还 ...
- Effective Java 目录
<Effective Java>目录摘抄. 我知道这看起来很糟糕.当下,自己缺少实际操作,只能暂时摘抄下目录.随着,实践的增多,慢慢填充更多的示例. Chapter 2 Creating ...
随机推荐
- MySQL忘记root密码的找回方法
(1)登录到数据库所在服务器,手工kill掉MySQL进程: kill ' cat /mysql-data-directory/hostname.pid' 其中,/mysql-data-dir ...
- Android 学习笔记之Volley(八)实现网络图片的数据加载
PS:最后一篇关于Volley框架的博客... 学习内容: 1.使用ImageRequest.java实现网络图片加载 2.使用ImageLoader.java实现网络图片加载 3.使用NetWork ...
- Windows Azure Web Site (17) 设置Web App TimeOut时间
<Windows Azure Platform 系列文章目录> 我们在开发Azure Web App的时候,如果页面加载时间过长,可能需要设置Time Out时间. 在这里笔者简单介绍一下 ...
- mysqldump: Couldn't execute 'show table status '解决方法
执行:[root@host2 lamp]# mysqldump -F -R -E --master-data=2 -p -A --single-transaction 在控制台端出现 mysqld ...
- MEF(Managed Extensibility Framework )的入门介绍
1.什么是MEF MEF是一个来自于微软协作构建扩展应用的新框架,它的目的是在运行中的应用中添加插件.MEF继承于.NET 4.0 Framework平台,存在于各种应用平台的系统程序集中 2.程序集 ...
- Redis持久化-数据丢失及解决
Redis的数据回写机制 Redis的数据回写机制分同步和异步两种, 同步回写即SAVE命令,主进程直接向磁盘回写数据.在数据大的情况下会导致系统假死很长时间,所以一般不是推荐的. 异步回写即BGSA ...
- 【C#进阶系列】02 PE文件,程序集,托管模块,元数据——还是那个Hello world
好了,还是这张图,还是一样的Hello world. 因为本章其实很多都是讲一些命令行编译啊什么鬼的配置类的东西,要用的时候直接百度或者回头查书就可以了, 所以了解一下也就行了,也没有记录下来,接下来 ...
- Discuz DB层跨库映射关系表名前缀BUG修复后产生的新bug
新的逻辑引入了新的bug,会导致在跨多库连接时,产生表名前缀映射混乱,需要再做逻辑上的修复. function table_name($tablename) { if(!empty($this-> ...
- [PHP] 实现路由映射到指定控制器
自定义路由的功能,指定到pathinfo的url上,再次升级之前的脚本 SimpleLoader.php <?php class SimpleLoader{ public static func ...
- lnmp+phpmyadmin配置与出现问题
本博客归moka同学(新浪微博:moka同学)本人亲自整理,如有使用,请加链接注明出处. lnmp 安装完全后,配置phpmyadmin .其访问方式为 http://202.18.400.379/p ...