好久没看书了。。。。。。。。。。。。惭愧,好吧从现在开始,并发好长时间没用了,有点忘了,看一下并发实践,总结一下
线程:有自己的程序计数器,栈,局部变量
并发的问题, 影响以下
安全,活跃性,和性能
安全,是指在Race condition 情况下,访问共享数据出现的错误
活跃性是指,包括并发引起的死锁,饥饿,活锁等情况,引起活跃性问题的根本原因是线程执行的时序,导致访问某些共享数据的时候出现问题
一些,JAVA中特别的类:
Timer, TimerTask 需要访问其他对象的数据来完全任务, 会引起安全的问题, 但是如果TimerTask访问的对象是线程安全的,那么就能把线程安全的问题,封装在对象内部
Servelt, Servlet 需要线程安全, 因为网络并发客户端访问, 通常我们使用的servlet都是线程安全的,因为我们定义的servlet是无状态的, 没有自身的状态数据, 但是如果一旦定义了servlet的状态变量,那么必须要做线程方面的考虑
本身JavaEE定义的HttpSession, ServletContext都应该是线程安全的,不然不能使用
具体讲讲安全性的问题
首先什么样的并发是安全的,所有的Shared, Mutable数据可以被正确的访问的并发程序是正确的
比如 ++ 操作, 就是一个"读取--修改--写入“的过程,这样就肯定了++操作在并发时是不安全的,需要被保护
保护的手段,无非是实现操作的原子性,而操作的原子性可以用,使用原子变量或者同步来实现
而RaceCondition是导致数据不一致性的重要原因,最常见的就是 Check then Act, 也就是检查到了不正确的数据
在Singleton模式中,延迟加载的实现中,需要对getInstance()方法进行同步, 不然就会在并发中产生典型的Check then Act错误
同时对于,一个操作中涉及到的所有变量都需要使用一个锁,或者说封装在一个原子操作中
对于那些被并发访问的共享可变变量,不只是在修改时需要同步,对于它的访问同样需要持有锁,整个变量应该是被锁保护的
重入, 简单的举个例子, 在子类中重写父类的synchronized方法, 并且调用父类的这个方法, 这时候在子类方法中其实已经持有了这个锁,调用父类方法的时候,就是锁的重入, 如果没有重入,那么就会导致死锁
无状态的对象,一定是线程安全的、
不可变对象,也一定是线程安全的,满足一下条件的对象,是不可变对象
1. 对象创建后,状态不能修改
2. 对象的所有域都是final
3. 对象正确创建(没有在构造方法中泄露this等)
对于执行时间较长的操作,不要持有锁
对于加锁的作用,有两方面,一个是实现操作的原子性,保持互斥,另一个就是保证数据的可见性,即在更新后,其他线程可以及时访问到新的对象
volatile 修饰符,实现了一个弱同步的功能, 可以用于保证数据的可见性
对于非volatile的long和double对象, 在JVM对它们进行读取和写入的时候,会分成两个32位的数据操作,可见是不安全的
而一旦使用了volatile就能保证数据在访问的时候看到的是最新值,因为volatile会让对象保存在处理器可见的地方,而不是寄存器等,同时,volatile 不是加锁,因此不会阻塞线程,正确的使用volatile厄方式: 确保它们自身状态的可见性, 确保它们所引用对象的可见性, 以及标识一些重要程序生命周期中事件的发生
总之,volatile多被用于标识状态,不太经常变化的对象
使用volatile,要满足以下条件
1. 读变量的写入,不依赖于对象的当前值,或者能够保证只在一个线程中更新变量
2. volatile变量不会与其他变量一起,涉入不变性条件
3. 在访问时不需要加锁
如何正确的发布一个对象
注意: 不要在对象的构造函数中启动一个线程,这样会导致对象的this泄露
线程封闭的对象,可以安全发布, 如果做到线程封闭呢?
ad-hoc线程封闭, 指的是线程封闭的责任有程序来实现承担
栈封闭, 就是使用局部变量
ThreadLocal, 不是很明确(????????)
上面的2步都是为了创建一个不可变或者线程安全的对象来发布
对于可变对象呢? 要使用一种安全的方式来发布
原则是: 对象的引用和对象的状态必须同时对所有线程可见
方法,可以将对象放入线程安全的容器,包括HashTable, synchronizedMap, ConcurrentMap
Vector, CopyOnWriteArrayList, CopyOnWriteArraySet, synchronizedLis, synchronizedSet,
BlockingQueue, ConcurrentLinkedQueue中
使用数据传递机制Future, Exchanger(后面具体解释), 来安全发布
对于一个静态构造的对象,最简单和最安全的发布方式,就是静态初始化器
public static Holder holder = new Holder(42)
因为静态构造器是在JVM类初始化阶段执行的
总结一下,对象发布
1. 不可变对象可以任何方式发布
2. 事实不可变对象通过安全的方式发布
3. 可变对象,则必须是线程安全或者某个锁保护的,在通过安全方式发布