Lock体系
1.lock与Synchronize比较:
(1)Synchronize是一个关键字,JAVA语言内置的特性;Lock是一个类,通过这个类实现同步访问。
(2)Synchronize不需要关心锁的获取与释放;lock需要手动获取释放锁,比较灵活。
2.AQS(AbstractQueuedSynchronizer):
(1)是什么?
AQS采用了模板方法设计模式,实现了同步状态的管理,线程排队等底层操作;具体的实现者只需要通过AQS提供的模板方法实现同步组件的语义即可。
(2)AQS如何维护锁状态?
定义了一个int类型的state,用volatile修饰保证内存可见性。1表示锁占用,0表示没被占用。另外采用CAS改变state状态保证线程安全。
(3)AQS如何实现锁的获取?
1.执行acquire方法,获取同步状态,即state。
2.获取成功执行线程逻辑。
3.获取失败,需要将线程加入同步队列。
4.同步队列为空则将线程节点入队。
5.不为空,CAS入队。
获取队列的线程:
1.当前节点是第一个吗?
2.不是则将节点状态设为SIGNAL,并将线程堵塞。
3.是第一个,尝试获取同步状态,成功将当前节点设为头结点,并执行线程逻辑。失败同2。
(4)如何使用?
class Sync extends AbstractQueuedSynchronizer {
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
继承AQS重写tryAcquire方法,用CAS改变状态,成功代表获取了锁。
3.ReentrantLock:
实现了Lock接口,有个内部类继承了AQS。支持重入性,还支持公平锁和非公平锁两种方式。
(1)重入性:
当一个线程获取锁后再次获取该锁不会堵塞。ReentrantLock通过对同步状态计数的方式实现重入性。即线程获取锁state加1,释放锁减1。当state为0时才使完全释放锁。
(2)公平锁和非公平锁:
公平与不公平是相对于锁而言的:
公平锁是线程请求锁后,如果同步队列为空则尝试加锁,不为空要将线程加入到同步队列中。
非公平锁是线程请求锁后,不管同步队列是否为空都会尝试加锁。
公平锁保证请求资源时间上的绝对顺序,这就需要频繁的上下文切换;而非公平锁降低一定的上下文切换,保证系统更大的吞吐量,不过非公平锁可以会导致有些线程很久获取不到锁。
4.Condition机制:
Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。
Condition实现了一个等待队列,它是一个单向队列。当调用await方法时会将线程加入到等待队列,并将锁释放;调用signal或者signalAll时会将线程从等待队列移动到AQS定义的同步队列里。
5.CountDownLatch:
join操作可以实现两个线程间的同步关系,如果有很多线程需要同步,比如A,B,C执行完后才执行D,这样join就没办法实现。所以引入了CountDownLatch,它也是内部类继承了AQS。源码如下:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
初始化时将state设为count,获取锁时只有state为0。调用countDown()方法相当于释放锁。所以我们知道当state不为0时会将线程加入到同步队列,通过countDown()减少state的值,当state为0时同步队列中的线程开始执行。