`

Java Concurrency In Practice读书笔记之组合对象

阅读更多

组合对象这一章主要讲述的是如何将线程安全或者非线程安全的组件以一种安全的方式组合成更大的组件或程序,并如何保证不会让程序意外破坏这些组件的线程安全性

设计线程安全类的过程中的三个基本要素

  1.    对象状态-----变量
  2.  状态----变量---不变约束,后验操作
  3.  同步策略

 

任何一个类都是由各种自身的变量来维护其状态的,因此我们要设计一个线程安全的类必须确定通过什么的同步策略来保证多线程环境下类的状态变量满足其自身的不变约束。在下面的这段代码中,就是通过synchronized 关键词这一同步策略来保证Counter类value这一变量的不变约束,大于零小于Long.MAX_VALUE

   @ThreadSafe

public class Counter {
	
	private long value = 0L;
	
	public synchronized long getValue()
	{
		return value;
	}
	
	public long increment()
	{
		if(value == Long.MAX_VALUE)
		{
			throw new IllegalStateException("Counter OverFlow");
		}
		return value++;
	}
}

  线程限制---将不安全的组件组装成安全的组件

当一个对象是不安全的时候,我们仍然有许多技术可以把它安全地用在多线程环境中。实例限制,即保证它只能同时被单一线程访问,但是被限制的对象一定不能逸出到它期望的可用范围之外。可以把对象限制在类实例(比如私有的类成员)、语汇范围(lexical scope,比如本地变量)或线程(比如对象在线程内部从一个方法传递到另外一个方法,不过前提是该对象不被跨线程访问)。下面的代码就是一个这方面的简单例子。

public class PersonSet {
	private final Set<Person> mySet = new HashSet<Person>();
	
	public synchronized void addPerson(Person p)
	{
		mySet.add(p);
	}
	
	public synchronized boolean containsPerson(Person p)
	{
		return mySet.contains(p);
	}
}

        非线程安全的HashSet管理着PersonSet的状态,不过由于mySet是私有的,不会逸出,因此HashSet被限制在PersonSet中,唯一可以访问mySet的方法都通过PersonSet的内部锁加以保护,确保在某一时间点只有一个线程能访问。

JDK中有很多线程限制的实例,如Collections.synchronizedList及其同族的方法。

很明显,Java内置的锁就是线程限制的实现。这里需要指出的是使用使用私有锁的好处,下面的代码就是一个私有锁得例子。

 

public class PrivateLock {

	private final Object myLock = new Object();
	Person person;
	
	void someMethod()
	{
		synchronized(myLock)
		{
			//访问或者修改person的状态
		}
	}
}

 

  使用私有锁可以封装锁,使得客户代码无法得到它,因此避免了客户代码涉足它的同步策略,同时还可以避免死锁。

委托线程安全----如何将线程安全的组件组合成线程安全的类

当设计一个类时,如果其状态变量都是线程安全的组件,那么我们是可以将类的线程安全性委托给这些线程的组件还是需要另外在这些组件上再加一层安全的外衣呢?这需要视情况而定。下面的两个例子代表了不同的情况

可以委托安全

public class VisualComponent {
	
	private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
	private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
	
	public void addKeyListener(KeyListener keyListener)
	{
		keyListeners.add(keyListener);
	}
	
	public void addMouseListener(MouseListener mouseListener)
	{
		mouseListeners.add(mouseListener);
	}
	
	public void removeKeyListener(KeyListener keyListener)
	{
		keyListeners.remove(keyListener);
	}
	
	public void removeMouseListener(MouseListener mouseListener)
	{
		mouseListeners.remove(mouseListener);
	}
}

需要安全的外衣

public class NumberRange {
	
	private final AtomicInteger lower = new AtomicInteger(0);
	private final AtomicInteger upper = new AtomicInteger(0);
	
	public void setLower(int i)
	{
		if(i > upper.get())
		{
			throw new IllegalStateException("Can't set lower to "+ i + "> upper");
		}
		lower.set(i);
	}
	
	public void setUpper(int i)
	{
		if(i < lower.get())
		{
			throw new IllegalStateException("Can't set lower to "+ i + "< lower");
		}
		upper.set(i);
	}
	
	public boolean isInRange(int i)
	{
		return (i >= lower.get() && i <= upper.get());
	}
}

 VisualComponent 可以把本身的线程安全性委托给其两个线程安全的组件keyListeners和mouseListeners。

 

但是NumberRange 就不可以把线程安全性委托给lower和upper,虽然它们都是线程安全的。这是因为在lower和upper不是相互独立的线程安全变量,它们各自的锁不能保证其setLower和setUpper的原子性。

如何向已有的线程安全类添加功能

通常来说,扩展类、客户端加锁和组合都可以实现向已有的线程安全类添加功能这一需求。但是我们更倾向于使用组合,因为扩展类和客户端加锁会破坏同步策略的封装性,下面通过两段代码来说明这一情况。

 

扩展类

public class BetterVector<E> extends Vector<E> {
	
	public synchronized boolean putIfAbsent(E x)
	{
		boolean absent = !contains(x);
		if(absent)
		{
			add(x);
		}
		
		return absent;
	}

}

 扩展后,同步策略的实现被分布到多个独立的维护的源代码文件中,如果基类和子类选中了不同的锁来保护它的状态,那么其同步策略会被改变,从而可能造成不能再用正确的锁控制对基类的并发访问,那上面的例子来说,如果Vector内部采用的不是synchronized关键词类实现锁,而是通过其它的锁,比如JDK5后提供的各种轻量级的锁来实现其同步策略,而此处的

BetterVector采用的是synchronized来实现同步策略,这一基类Vector的同步策略就被改变了。

客户端加锁

 

public class ImprovedList<E> {
	
	private final List<E> list = Collections.synchronizedList(new ArrayList<E>());
	
	public  boolean  putIfAbsent(E x)
	{
		synchronized(list)
		{
			boolean absent = !list.contains(x);
			if(absent)
			{
				list.add(x);
			}
			
			return absent;
		}

	}
}

 客户端加锁也破坏了锁的封装性,把锁和具体实现耦合了。

 

组合

 

public class ImprovedList<E> {
	
	private 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;
	}

}

 相对客户端加锁,组合就去掉了这种耦合,不过使用组合的时候,有一点需要注意,一旦使用了耦合,那么就不能再用list来操作,而都必须通过ImprovedList来进行操作。

 

这一章主要介绍了设计线程安全类需要注意的基本要素,并通过具体的例子说明如何来构造现场安全的类。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics