相对于synchronized它具备如下特点:
- 可中断,synchronized获得锁以后只能持有,不能被其它方法中断掉,但ReentrantLock锁可以被中断,即锁可以被破坏掉
- 可以设置超时时间,如果线程拿不到锁,按照synchronized的设置,线程就会进入EntryList中无限期的等待,直到获得锁才可以继续执行,但ReentrantLock可以设置等待时间,如果一定时间内还是竞争不到锁,即可以执行其他操作。
- 可以设置为公平锁,用以解决饥饿问题
支持多个条件变量,按照synchronized的处理方式,调用wait方法的线程会统一进入一个WaitSet中等待,相当于不管什么条件不满足都会进入WaitSet中,不去区分未满足的条件,这里的WaitSet就相当于一个条件变量。ReentrantLock则不同,可以支持多个条件变量,比如线程1因为不满足条件1而进入WaitSet1,线程2因为不满足条件2而进入WaitSet2,可以实现更细粒度的划分。
与synchronized一样,都支持可重入。
基本语法:
//获取锁
reentrantLock.lock();
try {
//临界区
}finally {
//释放锁
reentrantLock.unlock();
}
要保证lock方法和unlock方法成对出现,这样即使出现异常也能在finally中释放掉锁,以免其它线程迟迟获不到锁。
可重入:
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁住。
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try{
System.out.println("enter main");
m1();
}finally {
lock.unlock();
}
}
public static void m1(){
lock.lock();
try{
System.out.println("enter m1");
m2();
}finally {
lock.unlock();
}
}
public static void m2(){
lock.lock();
try{
System.out.println("enter m2");
}finally {
lock.unlock();
}
}
}
这里的lock.lock()和synchronized(lock)用法等价,都是对lock对象加锁。
可打断:
可打断是指,当线程获取不到锁时,不会无限制的等待锁释放,而是可以被interrupt方法打断等待。是一种避免线程死等锁的方式,可以减少死锁的发生。
lock.lockInterruptibly()方法是可被打断的加锁方法。
import com.sun.applet2.Applet2;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
try{
//如果没有竞争那么此方法就会获取lock对象锁
//如果有竞争就进入阻塞队列,可以被其它线程用interrupt方法打断
System.out.println("尝试获取锁");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("没有获得锁,返回");
return;//正常退出返回
}
try{
System.out.println("获取到锁");
}finally {
lock.unlock();
}
},"t1");
lock.lock();
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
用try-catch块包裹住lock.lockInterruptibly()方法,因为有可能被interrupt方法打断,所以要放在try块中,如果确实被打断,那么则会进入catch块中报异常,同时正常退出。如果没有被打断,当竞争到锁的时候则会执行第二个try块中的内容。
锁超时:
与被动的interrupt()方法打断线程等待相比,锁超时提供一种主动的避免死等的方式。
这里用到的方法是ReentrantLock对象的trylock()方法与trylock(long timeout)方法,该方法返回Boolean值,如果返回True代表获取到锁,返回False代表没有获取到锁。
trylock()方法:
import com.sun.applet2.Applet2;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("尝试获取锁");
if(!lock.tryLock()){
System.out.println("获取不到锁");
return;
}
try{
System.out.println("获得到锁");
}finally {
lock.unlock();
}
},"t1");
lock.lock();//主线程先一步获得锁
System.out.println("获得到锁");
t1.start();
}
}
trylock方法会尝试获取锁,如果获取到锁会正常执行,如果获取不到锁不会等待,而是会立刻不再竞争锁,从而继续执行代码。
这里的lock.lock()与lock.tryLock方法均是对一个对象上锁,可以保证互斥。
trylock(有参)方法:
trylock(有参)方法如果获取不到锁,会等待一段时间,如果这段时间内获取到锁即正常执行,获取不到则不再竞争锁,转为继续执行代码。
import com.sun.applet2.Applet2;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
System.out.println("尝试获取锁");
try {
if(!lock.tryLock(2, TimeUnit.SECONDS)){
System.out.println("获取不到锁");
return;
}
} catch (InterruptedException e) {
//这里如果在等待时间内被interrupt,也会退出等待状态,进入此catch块中作异常处理
e.printStackTrace();
System.out.println("获取不到锁");
return;
}
try{
System.out.println("获得到锁");
}finally {
lock.unlock();
}
},"t1");
lock.lock();//主线程先一步获得锁
System.out.println("获得到锁");
t1.start();
Thread.sleep(1000);
System.out.println("释放了锁");
lock.unlock();//手动释放锁
}
}
公平锁:
公平锁是指,当锁被释放时,竞争锁的线程们不会按照顺序获取锁,而是一拥而上地竞争锁,synchronized是不公平锁,ReentrantLock默认是一种不公平锁,在构造方法中可以传入True设置为公平锁。
一般公平锁的使用情况较少,因为会降低线程的并发性。