线程带来的问题:a)安全性问题b)活跃性问题c)性能问题
要编写线程安全的代码其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问
Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式,”同步”这个术语还包括volatile类型的变量,显示锁以及原子变量
在编写并发应用程序时,一种正确的编程方法是:首先使代码正确运行,然后在提高代码的速度。
完全有线程安全类构成的程序并不一定就是线程安全的,而在线程安全类中也可以包含非线程安全的类
线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的
无状态对象一定是线程安全的
无状态对象、原子性、竟态条件、符合操作
当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竟态条件。最常见的竟态条件类型就是”先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动作
计数器,可以通过现有的线程安全类实现如AtomicLong
在实际情况中,应尽可能地使用现有的线程安全对象(如AtomicLong)来管理类的状态
同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。
重入意味着获取锁的操作粒度是”线程”,而不是”调用”
每个共享的和可变的变量都应该只由一个锁来保护,从而是维护人员知道是那一个锁
一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问,如Vector
并非所有数据都需要锁的保护,只有被多个线程同时访问的可变数据才需要通过锁来保护
对于每个包含多个变量的不可变性条件,其中涉及的所有变量都需要由同一个锁来保护
无论是执行计算密集的操作,还是在执行某个可能阻塞的操作,如果持有锁的时间过长,那么都会带来活跃性或性能问题,当执行时间较长的计算或者可能无法快速完成的操作时(如I/O),一定不要持有锁
只要有数据在多个线程间共享,就使用正确的同步
可见性问题,产生失效值,非原子的64位操作问题,使用volatile声明或同步保护
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
Volatile变量的正确的使用方式包括:确保它们自身状态的可见性,确保它们所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生(例如,初始化或关闭)
调试提示,在启动JVM时指定 –servcr命令,将进行更多优化,比如将循环中未被修改的变量提升到循环外部,发现无线循环
加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性
当且仅当满足一下所有条件时,才应该使用volatile变量:a)对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值b)该变量不会与其他状态变量一起纳入不变性条件中c)在访问变量时不需要加锁
当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭
线程封闭技术:Ad-hoc/栈封闭/ThreadLocal类
满足同步需求的另一种方法是使用不可变对象:某个对象在被创建后其状态就不能被修改。不可变对象只有一种状态,且有构造函数来控制
不可变:a)状态不可修改b)所有域都是final类型c)正确的构造过程
可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时都必须使用同步
要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全的发布:a)在静态初始化函数中初始化一个对象引用b)将对象的引用保存到volatile类型的域或者AtomicReferance对象中c)将对象的引用保存到某个正确构造对象的final类型域中d)将对象的引用保存到一个由锁保护的域中
通过容器安全发布对象:a)通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中b)通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中c)通过将某个元素放入BlockingQuere或者ConcurrentLinkedQuere中
当获得对象的一个引用时,需要知道在这个引用上可以执行哪些操作。在使用它之前是否需要获得一个锁,是否可以修改它的状态,或者只能读取它
在并发程序中使用和共享对象时,可以使用一些实用的策略:a)线程封闭b)只读共享c)线程安全共享d)保护对象