第二部分:并发工具类14->Lock和condition(上)
1.并发领域核心问题互斥
同一时刻只能一个线程访问共享资源。
Lock解决互斥问题
2.并发领取核心问题同步
线程之间如何通信,协作
Condition解决同步问题
3.为什么要重复造轮子
synchronized已经实现管程,为什么要再实现一遍管程
synchronized性能不好
synchronized在死锁方面,无法破坏不可抢占条件,synchronized申请不到,线程直接阻塞状态了,就是线程的BLOCK状态,线程没有cpu执行权限,而且也不会释放占用的资源
4.重复造的轮子优势是啥
1.响应中断。可以接受中断信号,唤醒线程,就会释放锁,破坏了不可抢占条件。(自己获取的锁,别人抢不到)
2.支持超时。在一段时间内没有获取锁,不进入阻塞状态,而是返回错误,线程也会释放持有的锁,可以破坏不可抢占条件。
3.非阻塞获取锁。获取锁失败,不进入阻塞状态,而直接返回。这个线程有机会会释放曾经的锁
就可以弥补synchronized问题。
对应到Lock接口上的三个方法
// 支持中断的API
void lockInterruptibly()
throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();
5.Lock如何保证可见性
synchronized中的可见性,是因为happens-before规则,
synchronized的解锁happens-before的加锁操作
那么Lock呢?
class X {
private final Lock rtl =
new ReentrantLock();
int value;
public void addOne() {
// 获取锁
rtl.lock();
try {
value+=1;
} finally {
// 保证锁能释放
rtl.unlock();
}
}
}
利用了volatile相关的happens-before规则,
ReentrantLock里内嵌了一个volatile的成员变量state,获取锁的时候回读写state的值,解锁的时候也会读写state值。
class SampleLock {
volatile int state;
// 加锁
lock() {
// 省略代码无数
state = 1;
}
// 解锁
unlock() {
// 省略代码无数
state = 0;
}
}
1.顺序性原则,线程T1,value += 1 Happens-before 释放锁的操作unlock()
2.volatile变量规则,由于state = 1会先读取state,所以线程T1的unlock()操作happens-before与线程T2的lock()操作
3.传递性原则,先T1的value += 1 Happens-before 与T2的lock()操作
6.可重入锁
线程可重复获取同一把锁
class X {
private final Lock rtl =
new ReentrantLock();
int value;
public int get() {
// 获取锁
rtl.lock(); ②
try {
return value;
} finally {
// 保证锁能释放
rtl.unlock();
}
}
public void addOne() {
// 获取锁
rtl.lock();
try {
value = 1 + get(); ①
} finally {
// 保证锁能释放
rtl.unlock();
}
}
}
线程T1执行到(1)处,已经获取到锁rtl,在(1)调用get方法,会在(2)再次对rtl进行加锁操作,可以再次加锁成功,非重入锁,那么线程T1就会被阻塞。
7.公平锁与非公平锁
ReentrantLock,两个构造函数,一个无参,一个传fair参数的构造函数
fair参数代表的是锁的公平策略
true就是公平锁,false就是非公平锁
公平与不公平,看的是,锁释放的时候的策略
公平锁,锁释锁,从等待队列中唤醒一个等待的线程,谁等待的时间长,就唤醒谁
非公平锁,释放锁的时候,不提供这个公平保证,可能等待的时间短的反而先被唤醒.
8.用锁的最佳实践
1.永远只在更新对象的成员变量时加锁
2.永远只在访问可变的成员变量时加锁
3.永远不再调用其他对象的方法时加锁