希望将一些现有的线程安全组件组合为更大规模的组件或程序
设计线程安全的类
如果对象中所有的域是基本类型变量,那么这些域将构成对象的全部状态。例如,LinkedList的状态就包括该链表中所有节点对象的状态。要确保线程的安全性,就需要确保它的不变性条件不会在并发访问的情况下被破坏。
实例封闭
当一个对象被封装到另一个对象中时,能够访问被封装对象的所有代码路径都是已知的。通过将封闭机制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用非线程安全的对象。被封闭对象一定不能超出它们既定的作用域。来个例子
如果Person类是可变的,那么在访问从PersonSet中获得的Person对象时,还需要额外的同步。在java平台的类库中有很多线程封闭的实例,其中有些类的唯一用途是将非线程安全的类转化为线程安全的类。一些基本容器类并非线程安全的,例如ArrayList和Hash,但类库提供了包装器工厂方法(例如Collections.synchronizedList及其类似方法),使得这些线程非安全的类可以在多线程环境中安全地使用。这些工厂方法通过“装饰器”模式将容器类封装在一个同步的包装器对象中,而包装器能将接口中的方法都实现为同步方法,并将调用请求转发到底层的容器对象上。只要包装器对象拥有对底层容器对象的唯一引用,那么他就是线程安全的。
java监视器模式
把所有可变状态都封装起来,并由对象自己的内置锁来保护。如:
看到了有意思的部分了:
使用私有锁而不是对象的内置锁(或任何其他可通过公有方式访问的锁), 有许多优点。私有的锁对象可以将锁封装起来,是客户代码无法得到锁,但客户代码可以通过公有方法来获得锁。这是书上的原话,然而看不懂....。首先要搞明白的是私有锁和内置锁的区别,在网上看到一篇不错的博文:http://www.jb51.net/article/56440.htm。
1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段。
2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段。
3.私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock)。
4.内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
看来内置锁和对象锁是一回事了,只不过角度不同定义自然不一样。
示例:车辆追踪
一个用于调度车辆的“车辆追踪器”,例如出租车,警车,货车等。首先使用监视器模式来构建车辆追踪器,然后在尝试放宽某些封装性需求同时又保持线程安全性。
执行更新操作的线程通过从GPS设备上获取的数据或者调度员从GUI界面上输入的数据来修改车辆的位置
视图线程与执行更新操作的线程将并发地访问数据模型,因此该模型必须是线程安全的,下面的程序给出了一个基于java监视器模式实现的“车辆追踪器”,其中使用了程序清单4-5中的MutablePoint来表示车辆的位置。
虽然类MutablePoint不是线程安全的,但追踪器类是线程安全的。它所包含的Map对象和可变的Ponit对象都未曾发布,当需要返回车辆的位置时,通过MutablePoint拷贝构造函数或者deepCopy方法来复制正确值,从而生成一个新的Map对象,并且该对象中的值与原有Map对象中的key和value值都相同。
线程安全性的委托
示例:基于委托的车辆追踪器
构造一个委托给线程安全类的车辆追踪器。将车辆的位置保存到一个Map对象中,因此首先要实现一个线程安全的Map类ConcurrentHashMap。还可以用一个不可变的类Point来代替MutablePoint以保存位置,
如果使用最初的MutablePoint类而不是Point类就会破坏封装性,因为getLocations会发布一个指向可变状态的引用。这里我对监视器模式和委托之间的区别的理解是:监视器将所有可变状态封装,对这些状态的操作利用内置锁保护起来。而委托模式则是将这些状态的安全性委托给一些线程安全的容器来保护。上面的代码还有一点要注意的是:getLocations返回的是一个不可修改但却实时的车辆位置视图。
独立的状态变量
将线程的安全性委托给多个状态变量。只要这些变量是彼此独立的。
当委托失效时
状态变量之间存在着某些不变性条件,如:
此时仅靠委托机制并不足以实现线程安全性,这个类必须提供自己的加锁机制。
发布底层的状态变量
示例:发布状态的车辆追踪器
在这个版本中发布底层的可变状态。
在现有的线程安全类中添加功能
简而言之就是利用java所提供的安全类库,再添加我们自己的类,来确保我们要实现的程序功能的线程安全性。
客户端加锁机制
参考:https://www.cnblogs.com/yaowen/p/5983136.html
组合
当为现有的类添加一个原子操作时,有一种更好的方法:组合。
使用了java监视器模式来封装了现有的List,只要在类中拥有指向底层List的唯一外部引用,就能确保线程安全性。(注意这里的底层List实例是私有不可变的)
心得:这章的主要内容是当多个对象组合在一起时如何确保其线程安全性。感觉这章真的是很抽象,说了很多理论性的东西,现在还是有很多不清楚,不明白的地方,还是得靠多实践才能加深理解。