一、概念
ReentrantLock是接口lock的实现类,实现为基于CAS+AQS(AbstractQueuedSynchronizer构建锁和同步容器的框架)的可重入锁,默认为非公平锁,可通过构造器定义为公平锁。
二、原理
ReentrantLock获取锁的原理:先通过CAS判断锁资源是否被占用。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:
非公平锁:当排在队列队首获取锁资源,如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取,因为这时锁是未被占用的;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。
三、使用
1.可重入:同一线程,获取两次锁,释放两次锁。
public class reentrantlock {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();
lock.lock();
}catch (Exception e){
}finally {
lock.unlock();
lock.unlock();
}
}).start();
}
}
2.tryLock:尝试获取锁。获取失败返回fasle,执行逻辑。
public class trylocks {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
boolean flag = false;
Thread.currentThread().setName("线程A");
try {
System.out.println(Thread.currentThread().getName()+"开始执行");
flag = lock.tryLock(3, TimeUnit.SECONDS);
if(flag){
Thread.currentThread().sleep(5000);
System.out.println(Thread.currentThread().getName()+"获取到锁");
}else{
System.out.println(Thread.currentThread().getName()+"未获取到锁");
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(flag){
lock.unlock();
}
}
}).start();
new Thread(() -> {
boolean flag = false;
Thread.currentThread().setName("线程B");
try {
System.out.println(Thread.currentThread().getName()+"开始执行");
lock.tryLock();
flag = lock.tryLock(3, TimeUnit.SECONDS);
if(flag){
Thread.currentThread().sleep(5000);
System.out.println(Thread.currentThread().getName()+"获取到锁");
}else{
System.out.println(Thread.currentThread().getName()+"未获取到锁");
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(flag) {
lock.unlock();
}
}
}).start();
}
}
3.公平锁:在锁上等待时间最长的线程将获得锁的使用权。大部分情况下我们使用非公平锁,因为其性能比公平锁好很多,不需要cpu切换线程。但是公平锁能够避免线程饥饿。
public class sameLock {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(true);
for(int i =0;i<5;i++){
new Thread(() ->{
Thread.currentThread().setName("线程"+Math.random());
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取到锁");
lock.unlock();
lock.lock();
System.out.println(Thread.currentThread().getName()+"获取到锁");
lock.unlock();
}).start();
}
}
}
4.lockInterruptibly:用来接收线程被打断信号,接收到抛出异常,防止线程因为获取不到锁资源一直在阻塞状态。
public class Interruptibly {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("t1开始执行");
try {
lock.lock();
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println("t1执行结束");
});
Thread t2 = new Thread(() -> {
System.out.println("t2开始执行");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("t2被打断");
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println("t2执行结束");
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
long end = System.currentTimeMillis();
//等待时间超过5s,打断t2线程。
while (end-start<5000){
end = System.currentTimeMillis();
}
t2.interrupt();
}
}
四、ReentrantLock和synchronized比较
1.synchronized是java内置的关键字,ReentrantLock是接口lock实现类。都可重入。
2.ReentrantLock可以尝试获取锁,根据结果执行逻辑,synchronized直接等待。
3.ReentrantLock需要手动释放锁,synchronized由JVM管理。
4.ReentrantLock可设定为公平锁。