组合对象这一章主要讲述的是如何将线程安全或者非线程安全的组件以一种安全的方式组合成更大的组件或程序,并如何保证不会让程序意外破坏这些组件的线程安全性
设计线程安全类的过程中的三个基本要素
-
对象状态-----变量
- 状态----变量---不变约束,后验操作
- 同步策略
任何一个类都是由各种自身的变量来维护其状态的,因此我们要设计一个线程安全的类必须确定通过什么的同步策略来保证多线程环境下类的状态变量满足其自身的不变约束。在下面的这段代码中,就是通过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来进行操作。
这一章主要介绍了设计线程安全类需要注意的基本要素,并通过具体的例子说明如何来构造现场安全的类。
分享到:
相关推荐
本笔记将深入探讨《Java Concurrency In Practice》这本书中的核心概念,结合Guava库的实际使用案例,帮助读者理解并掌握Java并发编程的精髓。 首先,我们来了解Java并发的基础知识。Java提供了丰富的并发工具类,...
《Java Concurrency in Practice》中文版笔记
读书笔记和示例代码 包括 线程基础/同步sync/并发concurrent/容器/线程池 示例代码 JUC容器的源码解析 Synchronized 同步容器 Concurrent 并发容器: Map/List/Queue 阻塞队列 JUC锁: CountDownLatch 闭锁 AQS 锁的...
Addison.Wesley.Java.Concurrency.in.Practice.May.2006.chm Agile Java 测试驱动开发的编程技术.pdf Java 8 默认方法和多继承.docx Java NIO通信框架在电信领域的实践.docx java.png javaconcurrencyinpractice.pdf...
并发编程实战》阅读笔记 有关 Java 并发编程~ 别急别急,在重写它啦~,会补充一些新的内容进去,预计 2020/02 完成。 Content Java 并发编程基础 保证线程安全的两个角度 构造安全的并发应用程序 Java 并发高级主题 ...
书籍如《Effective Java》、《Java Concurrency in Practice》和《Head First Java》等,都是学习Java高级编程的经典之作。在线资源如Oracle的Java Tutorials和Stack Overflow上的问答,为解决实际问题提供了大量...
Addison.Wesley.Java.Concurrency.in.Practice.May.2006.chm Agile Java 测试驱动开发的编程技术.pdf Java 8 默认方法和多继承.docx Java NIO通信框架在电信领域的实践.docx java.png javaconcurrencyinpractice.pdf...
Java Concurrency in Practice 对象的共享 对象的组合 基础构建模块 JavaGC监控与优化 垃圾回收机制 垃圾回收机制的监控 优化垃圾回收机制 Apache的MaxClients参数详解及其在Tomcat执行FullGC时的影响 JavaEE 浅析...
【Java并发编程实战】的阅读笔记-英文版:Java Concurrency in Practice官网源代码地址: : 1.主要是对源代码作一些补充并能实际运行一下(以junit的方式)2.添加了java8newfeatures,主要是写了几个java8的新...
- **书籍**:《Effective Java》、《Java Concurrency in Practice》、《Clean Code: A Handbook of Agile Software Craftsmanship》等经典书籍深入讲解Java编程技巧和最佳实践。 - **社区与论坛**:Stack Overflow...