1、线程安全要素:对共享、可变状态的访问操作。
所谓状态,是指存储在状态变量(实例或者静态域)中的数据。对象的状态可能包括其他依赖对象的域。状态是一系列变量的集中体现,比如 int[] data; int size; 该对象的状态就包含两个变量。无状态的类一定是线程安全的。
共享是说这个状态将被多个线程访问。
有共享可变状态的类,如果有写操作,那么一定需要对状态进行全面的同步保护。
2、竞态条件(Race Condition):在并发编程中,由于不恰当的执行时序而出现不正确的结果。
常见的竞态条件类型是“先检查后执行(Check-Then-Act)”,比如HashMap的put操作,“如果某个位置空闲,就把新元素设置到这个位置”,在竞态条件下,只有一个元素被正确添加了。
3、复合操作:由多个原子操作组合而成。
比如a++; 这个操作包含了,读取-修改-写入三个原子操作。
比如 if (a!=0) c = b/a; 这个操作至少包含了,判断a不为0-计算b/a-写入c等原子操作。
比如 if (!vector.contains(element)) vector.add(element); ,判断vector不包含element-将element加入vector两个原子操作。
有些类虽然保证了一些方法的原子性,但是组合起来,还是需要额外的加锁机制。
4、程序清单2-8
这里service里面的两处synchronized同步,其实本质上也是“先判断后执行”,但是它是线程安全的。原因不是“先判断后执行”这种方式不需要复合同步,而是这里的业务逻辑要求很宽松,无论谁先执行,谁后执行,其实都不影响。
但是自己这么写程序千万要小心,非常容易出问题。
5、不变性条件:状态变量之间的约束关系,比如这两个变量 int[] data; int size; 之间的关系应该是,data中的数据数目=size。
当类的不变性条件设计多个状态变量时,那么不变性条件中的每个变量都必须由同一个锁来保护。因此可以在单个原子操作中访问或更新这些变量,从而确保不变性条件不被破坏。
6、简单性和性能
当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O),尽量不要持有锁。