设计线程安全的类
三个基本要素(应该说是步骤吧)
- 找出构成对象状态的所有变量
- 找出约束状态变量的不实性条件
- 建立对象状态的并发访问管理策略
举个栗子(这里声明为final类只是不能被继承,没别的功能)
public final class Counter {
private long value = 0;
/**
* @return the value
*/
public long getValue() {
return value;
}
public long increment() {
return ++value;
}
}
这是一个普通的计算器类
第一步找出所有对象状态 ,只有一个value。
第二步,找出约束状态变量的不变性条件
public long increment() {
if (value == Long.MAX_VALUE) {
throw new IllegalStateException("counter overflow");
}
return ++value;
}
第三步 建立并发访问策略。加上同步关键字
public final class Counter {
private long value = 0;
/**
* @return the value
*/
public synchronized long getValue() {
return value;
}
public synchronized long increment() {
if (value == Long.MAX_VALUE) {
throw new IllegalStateException("counter overflow");
}
return ++value;
}
}
java监视器模式
直接上代码比较容易理解,程序员的语言理解力对代码更敏感吧~
public class privateLock {
private final Object myLock = new Object();
int value;
void someMethod() {
synchronized (myLock) {
// 访问或修改value
}
}
}
使用私有的锁对象而不是对象的内置锁(或任何其他可通过公有方式访问的锁)。
私有的锁对象可以将锁封装起来,使客户代码无法得到锁。但客户代码可以通过公有方式来访问锁,以便参与 到同步策略中。
在Vector和hashtable类中都使用了java的监视器模式。在jdk中可以看见它们的代码选 一段出来 ,如下
public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
public synchronized ListIterator<E> listIterator() {
return new ListItr(0);
}
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>
* The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public synchronized Iterator<E> iterator() {
return new Itr();
}
那么问题是为什么把synchronized加到方法 内部而不是放在方法关键字上
分析一下
synchronized加在方法上就保证了这个对象只能串行的执行这个方法
而加在方法内部意味着同一时刻只能有一个线程持有这个对象的引用
那为什么只有Next和remove方法把对象本身做为锁加在了方法内部,试想一下,在一个线程调用next的时候 ,另一个线程把下一个给remove了,不就悲剧了。
扯远了,其实监视器模式就是为方法加上synchronized关键字,用于内置锁。
从现有的线程安全类中添加功能
public class BetterVector<E> extends Vector<E>
{
public synchroinzed boolean putIfAbsent(E x){
boolean absent=!contains(x);
if(absent)
add(x);
return absent;
}
}
如上。
客户端加锁机制
对于由Collections.synchronizedList封装的ArrayList,在原始类中添加一个方法或者对类进行扩展都行不通。
因为客户代码并不知道 在同步封装器工厂方法中返回的List对象的类型
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent) {
list.add(x);
}
return absent;
}
}
上面的这段代码并不能实现线程安全性,为什么 ?
看过上面的我对vector的分析 就应该知道 。这个方法是原子性的。但是相对于未编写的其它方法并不是原子性的。很简单。在你执行这个方法的时候另一个方法在执行removeall()。那不就悲剧了。
正确的方法 就是用synchronized(list){} 对list这个域进行加锁处理
组合
向现有类添加原子操作时一种更好的方法
public class ImprovedList<E> {
public final List<E> list;
public ImprovedList(List<E> list) {
this.list = list;
}
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent) {
list.add(x);
}
return absent;
}
public synchronized void clear() {
list.clear();
}
}
利用自身的锁为list做线程安全性的保护 。也就是。。加壳。当然对于一些特殊的操作,也是需要对list进行加锁的。
将同步策略文档化
也就是将同步策略记录下来,便于用户或后续维护人员进行维护。