fraternityjava 2020-06-26
保证线程安全的策略:
保证线程安全,就要避免Race Condition,竞争的存在能破坏ADT的RI,使数据混乱。
策略1.限制数据共享
将可变数据限制在单一线程内部,避免竞争,不允许任何线程直接读写数据。
核心思想:线程之间不共享mutable的数据类型
避免全局可变变量
2.共享不可变数据
使用不可变数据类型和不可变引用,避免多线程之间的race condition
关键词 final有用,只允许读,不允许写
不可变数据通常是线程安全的。
对于并发而言,有益的改变也是可能引起Race Condition的,需要加锁保证安全。
3.使用线程安全的数据类型
JDK提供了一些可变的线程安全的对象,这些对象对他们的每一个操作调用,都是以原子方式执行的,不会与其他操作交错(interleaving)
在使用了线程安全的包装器产生的对象之后,不要把原对象分享给其他线程,不要保留别名,一定要彻底销毁。
即使是在线程安全的集合类上,使用迭代器仍然是不安全的,除非使用lock机制,当时用迭代器访问集合时,当别的线程修改集合时,迭代器会失效,并抛出ConcurrentModificationException异常。
这个线程安全的数据类型只能保证其上某个操作是线程安全的,但是如果多个操作放在一起,仍旧不安全。
4.锁和同步
使用锁的机制,获得对数据独家修改权利,其他的线程均为被阻塞,不得访问。
拥有Lock的线程可独占式的执行该部分代码。其他的线程被阻塞直到锁被释放
注意要保证互斥,需要使用同一个锁
Monitor Pattern
一个监视器是一个类,它的所有方法都被人为的独占,所以在一个时间内只能有一个线程使用它的实例。
使用ADT自己做lock
同步机制会给性能带来极大影响,除非必要,否则不要使用
因此,需要尽可能减小lock的范围,避免在方法签名中加入synchronized,而且在方法代码内部更加精细的区分哪些代码有可能有threadsafe危险,为其加锁。
加锁的原则:
任何共享的mutable变量/对象必须被锁保护
涉及到多个mutable变量时,他们必须被同一个lock保护。
monitor pattern下,ADT的所有方法都要被同一个synchronized(this)保护
实现更大的原子操作:通过锁
死锁:
发生的条件
1.多个线程使用多个锁,且多个线程使用锁的顺序不同,可能就会导致互相等待对方释放锁,从而都无法运行下去而终止
死锁的例子:
solution
1.按顺序锁
2.粗密度的锁,只是用一个锁
2.解锁的时没有解除所有线程的因这个锁而造成的阻塞状态
没有使用signalAll方法,而是换成了singal方法
条件对象的方法
void await()
将该线程放到条件的等待集中
void singalAll()
解除该条件的等待集中所有线程的阻塞状态。
void singal()
随机解除一个该条件等待集中一个线程的阻塞状态
在同步方法(使用synchronized关键字)中使用的wait方法,同步方法使用内部锁,只有一个条件对象
void wait()
导致线程进去等待状态,该方法只能在一个同步方法或同步模块中调用
void notify()
随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态,只能在同步方法或同步模块中使用。
void notifyAll();
解除所有在该对象上调用wait方法的线程的阻塞状态,只能在同步方法和同步模块中使用。
当发现目前的数据情况无法执行方法时,可以将自己wait,释放锁(notify or notifyAll)让别人先执行
说明线程安全的规约
在代码中以注释的形式增加说明:该ADT采用了什么设计决策来保证线程安全。
采取了哪一种策略?
如果是后两种,还需要考虑对数据的访问都是原子的,不存在交错(interleaving)
限制数据共享(策略1)通常不是好策略,因为该策略要避免所有可变数据的共享,除非知道线程访问的所有数据,否则就无法彻底保证线程安全。