[Java 并发编程实战] 设计线程安全的类的三个方式(含代码)

发奋忘食,乐以忘优,不知老之将至。———《论语》

前面几篇已经介绍了关于线程安全和同步的相关知识,那么有了这些概念,我们就可以开始着手设计线程安全的类。本文将介绍构建线程安全类的几个方法,并说明他的区别。

我要讲的这几个构建线程安全类的方式是:

实例封闭。

线程安全性的委托。

现有的线程安全类添加功能。

另外,在设计线程安全类的过程中,我们需要考虑下面三个基本要素,遵循这三个步骤:

找出构成对象状态的所有变量。

找出约束状态变量的不变性条件。

建立对象状态的并发访问策略。

以上,就是这篇文章主要讲解的内容,下面章节分三个构建方法逐步展开说明,逐个分析,并附上自己测试过的实例代码,确保这篇文章分享的内容是经过验证的。

实例封闭

意思是将数据封装在对象内部,它将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。当一个非线程安全对象被封装到另一对象中时,能够访问被封装对象的所有代码路径都是已知的。这和在整个程序中直接访问非线程对象相比,更易于对代码进行分析。下面代码清单就是一个实例封闭的例子:

1import java.util.ArrayList;
2
3//ThreadSafe
4public class PointList{
5
6 //非线程安全对象 myList
7 private final ArrayList<SafePoint> myList = new ArrayList<SafePoint>();
8
9 //所有访问 myList 的方法都是用同步锁,确保线程安全
10 public synchronized void addPoint(SafePoint p) {
11 myList.add(p);
12 }
13 //所有访问 myList 的方法都是用同步锁,确保线程安全
14 public synchronized boolean containsPoint(SafePoint p) {
15 return myList.contains(p);
16 }
17 //所有访问 myList 的方法都是用同步锁,确保线程安全
18 //发布SafePoint
19 public synchronized SafePoint getPoint(int i) {
20 return myList.get(i);
21 }
22
23 //ThreadSafe(可发布的可变线程安全对象)
24 class SafePoint{
25 private int x;
26 private int y;
27
28 private SafePoint(int[] a) {this(a[0], a[1]);}
29
30 public SafePoint(SafePoint p) {this(p.get());}
31
32 public SafePoint(int x, int y) {
33 this.x = x;
34 this.y = y;
35 }
36 //使用同步锁,确保线程安全
37 public synchronized int[] get() {
38 return new int[] {x, y};
39 }
40 //使用同步锁,确保线程安全
41 public synchronized void set(int x, int y) {
42 this.x = x;
43 this.y = y;
44 }
45 }
46}

PointList 的状态由 ArrayList 来管理,但是 ArrayList 并非线程安全的。由于 ArrayList 私有并且不会逸出,因此 ArrayList 被封闭在 PointList 中。唯一能够访问 ArrayList 的路径都上同步锁了,也就是说 ArrayList 的状态完全有 PointList 内置锁保护,因而 PointList 是一个线程安全的类。Point 类的安全性放到后面讨论。

从这里例子可以看出,实例封闭可以非常简单的构建出线程安全的类。封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无需检查整个程序。当然,如果将一个本该封闭的对象发布出去,那么也会破坏封闭性。

线程安全性的委托

如果类中的各个状态已经是线程安全的,那么是否需要再增加一个线程安全层的封装呢?
具体问题具体分析,这种需要视情况而定。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgdjxg.html