ReentrantLock与Synchronized
ReentrantLock是一种可重入的独占锁,相对于synchronized,ReentrantLock具备如下特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
- 与synchronized一样,都支持可重入
在使用时要注意4个问题:
- 默认情况下ReentrantLock为非公平锁而非公平锁;
- 加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;
- 加锁操作一定要放在try代码之前,这样可以避免未加锁成功又释放锁的异常;
- 释放锁一定要放在finally中,否则会导致线程阻塞。
工作原理
当有线程调用lock方法的时候:如果线程获取到锁了,那么就会通过CAS的方式把AQS内部的state设置成为1。这个时候,当前线程就获取到锁了。只有首部的节点(head节点封装的线程)可以获取到锁。其他线程都会加入到这一个阻塞队列当中。如果是公平锁的话,当head节点释放锁之后,会优先唤醒head.next这一个节点对应的线程。如果是非公平锁,允许新来的线程和head之后唤醒的线程通过cas竞争锁。
Condition
相当于synchronized中的wait()和notify()机制,通常与Lock接口(比如ReentrantLock)一起使用。调用Condition的await()和signal()方法,都必须在lock保护之内。
public class Main {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public static volatile boolean flag = false;
private void lockCondition() throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("t1线程抢到锁");
while (!flag) {
System.out.println("t1线程等待");
condition.await();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
System.out.println("t1线程释放锁");
}
System.out.println("线程t1执行完成");
}
}, "t1").start();
Thread.sleep(3000);
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("t2线程抢到锁");
flag = true;
condition.signal();
} finally {
lock.unlock();
System.out.println("t2线程释放锁");
}
System.out.println("t2线程执行完成");
}
}, "t2").start();
}
public static void main(String[] args) throws InterruptedException {
new Main().lockCondition();
}
}
AQS(AbstractQueuedSynchronizer)
- volatile int state : 表示资源的可用状态 0 可用 1 已被占用 >1表示线程重入(线程通过cas对state写入成功就是加锁成功)
- 两种队列:
2.1 同步等待队列:主要用于维护获取锁失败时入队的线程。
2.2 条件等待队列:调用await()的时候会释放锁,然后线程会加入到条件队列,调用signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁。
- ReentrantLock用到tryAcquire和tryRelease方法,是独占锁