1.相对于synchronized具备的特点:
1) 可打断:避免死等2)可以设置超时时间:避免死等3)可以设置为公平锁:防止饥饿4)支持多个条件变量:可以指定唤醒某个特定线程5)需要手动释放锁:如果执行过程中出现异常,并不会自动释放锁, 需要写finally块手动释放锁
2. 可重入:
设置ReentrantLock为类的静态变量,在main线程中调用m1()方法,m1()方法加锁reentrantlock.lock(),m1中又调用m2(),m2也加锁,m2()调用m3(),m3方法中也加锁。
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
reentrantLock.lock();
try {
log.debug("execute method1");
method2();
} finally {
reentrantLock.unlock();
}
}
public static void method2() {
reentrantLock.lock();
try {
log.debug("execute method2");
method3();
} finally {
reentrantLock.unlock();
}
}
public static void method3() {
reentrantLock.lock();
try {
log.debug("execute method3");
} finally {
reentrantLock.unlock();
}
}
运行结果:说明ReentrantLock是可重入锁
3.可打断(打断等待):
当t1线程使用synchronized加锁时,如果t2线程获取不到锁,只能自旋等待或者进入EntryList中等待,无法打断等待
ReentrantLock锁可以打断t2线程的等待
下面代码的解释:
定义类变量ReentrantLock,t1线程使用lockInterruptibly()方法加锁,然后睡眠,睡眠不会释放锁。
t2线程在t1线程睡眠时,获得时间片,尝试获得reentrantlock锁,没有获得成功,进入阻塞队列等待,调用t2.interrupt()方法打断t2线程的等待,进入catch块,打印"t2没有获得到锁,被打断,结束t2线程等待"和异常信息
使用 lockInterruptibly()方法加锁,需要把加锁代码放在try块中,当被打断等待时,执行catch块中的代码,打印异常信息,并且return结束当前线程
private static ReentrantLock reentrantLock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
try {
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("t1没有获得到锁,被打断,结束t1线程等待");
return;
}
try {
System.out.println("t1获得到锁");
System.out.println("hello,t1");
Thread.sleep(1000000);
}catch (Exception e){
System.out.println(e);
}finally {
reentrantLock.unlock();
}
});
Thread t2=new Thread(()->{
try {
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("t2没有获得到锁,被打断,结束t2线程等待");
return;
}
try {
System.out.println("t2获得到锁");
System.out.println("hello,t2");
}catch (Exception e){
System.out.println(e);
}finally {
reentrantLock.unlock();
}
});
t1.start();
Thread.sleep(1);
t2.start();
t2.interrupt();
}
4.设置超时时间:
tryLock(1,TimeUnit.SECONDS):是尝试获取锁1s,返回一个boolean结果,通过结果判断是否继续向下执行。
需要放在try块中,此方法也是可以被打断的,被打断后执行catch块中的内容。
注意:try和catch中都需要return,如果此方法被打断后,进入catch,如果catch中没有return,则会继续执行try..catch块外面的内容,如图中的打印hello
private static ReentrantLock reentrantLock=new ReentrantLock();
public static void main(String[] args) {
Thread t1=new Thread(()->{
try {
if(!reentrantLock.tryLock(10, TimeUnit.SECONDS)){
System.out.println("没有获取到锁");
return;
}else{
System.out.println("获取到锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("获取不到锁");
return;
}
System.out.println("hello");
});
reentrantLock.lock();
t1.start();
}
5.公平锁
使用synchronized加锁时,如果t1线程加上锁,t2,t3,t4线程则需要进入EntryList中等待,t1线程释放锁,唤醒t2,t3,t4 ,但t2,t3,t4不会按照先进先出的顺序获得锁,会非公平的抢占锁。
ReentrantLock reentrantLock = new ReentrantLock(true); 设置为true,则reentrantLock变为公平锁,即获得锁的顺序按照先进先出的顺序。
6.条件变量/await()/signal()
如果用synchronized对object加锁,如果t1线程获得了锁,但是不满足继续执行的条件,只能调用object.wait()方法,放弃锁,如果有多个线程不满足条件,都会放弃锁进入WaitSet中等待
一旦某个线程的条件满足,需要唤醒时,没办法特定唤醒某个线程,只能随机唤醒object.notify(),或者全部唤醒object.notifyAll()
如果用ReentrantLock加锁,可以使用多个条件变量,特定唤醒某个线程。
如下代码的含义:
t1线程先获取reentrantlock锁,但是c1是false,不满足条件继续执行,Condition C1condition=reentrantLock.newCondition()创建一个t1线程的特定条件,调用C1condition.await()方法放弃锁。
主线程获得reentrantlock锁,将c1设置为true,唤醒t1线程,调用C1condition.signal()方法,这样可以指定唤醒t1线程,t1线程继续向下执行。
注意:使用await()和signal()时,与synchronized的wait()和notify()一样,也需要提前获取锁对象。
static ReentrantLock reentrantLock=new ReentrantLock();
static volatile boolean c1=false;
static Condition C1condition=reentrantLock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
reentrantLock.lock();
try {
System.out.println("t1线程获得锁");
if(!c1){
System.out.println("t1线程不满足条件,放弃锁");
C1condition.await(100,TimeUnit.SECONDS);
}
System.out.println("t1线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
});
t1.start();
Thread.sleep(1000);
reentrantLock.lock();
c1=true;
C1condition.signal();
reentrantLock.unlock();
}