ReentrantLock是Lock接口的一个实现,可以用于替代synchronized。
使用ReentrantLock可以完成类似synchronized(this)的功能,需要注意的是,就算线程已经执行完毕,Lock也不会自动释放锁,必须要手动释放锁!!!
与synchronized不同的是:使用synchronized锁定的话如果遇到异常,JVM会自动释放锁,但是Lock必须手动释放锁,因此经常在finally中进行锁的释放。
public class ReentrantLockDemo {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.print(i + " ");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();// 比较一下未释放锁前后的执行区别
}
}
void m2() {
lock.lock();
System.out.println("m2...");
lock.unlock();
}
public static void main(String[] args) {
ReentrantLockDemo r1 = new ReentrantLockDemo();
new Thread(r1::m1).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r1::m2).start();
}
}
执行结果:
将m2()方法修改成如下形式,再次执行,观察结果:
/**
* 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行, 可以根据tryLock的返回值来判断是否锁定,
* 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以注意unLock的处理,必须放在finally中。
* 作者: mht
* 时间:2018年9月15日-下午10:07:42
*/
void m2() {
// boolean locked = lock.tryLock();
// System.out.println("m2..." + locked);
// if (locked) lock.unlock();
boolean locked = false;
try {
locked = lock.tryLock(3, TimeUnit.SECONDS);
System.out.print("m2 resume... ");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked)
lock.unlock();
}
}
执行结果(修改m2后继续执行第一段代码中的main方法):
Lock 的tryLock()方法支持有参和无参,根据实际需要可以指定具体的等待时间,或不进行等待(如注释掉的代码)。
在多线程共同请求同一个Lock时,有时会希望某个线程能够被打断,从而终止等待的状态。
Lock.lockInterruptibly()方法同样可以请求一个可用的锁对象,如果锁对象不可用,则进入阻塞状态。但是与Lock.lock()不同的是,当程序中调用Thread.interrupt()方法打断线程时,前者会做出响应,而后者并不会。当然,即便是获得了锁对象,线程依然可以被打断,但是要记住在finally块中释放被打断线程中持有的锁对象。
public class InterruptWaitingDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lock();
System.out.println("t1 start");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
System.out.println("t1 end");
} catch (InterruptedException e) {
System.out.println("interrupted!");
} finally {
lock.unlock();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
try {
lock.lockInterruptibly();
System.out.println("t2 start");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end");
} catch (InterruptedException e) {
System.out.println("Interrupted!");
} finally {
System.out.println("finally:lock = " + lock.tryLock());
lock.unlock();
}
}, "t2");
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();// 打断线程 t2,不论线程是否获得锁。
}
}
上述代码中,线程t1持有lock对象,并一直锁定,t2无法得到这把锁,如果在线程t2中以原来的lock()方法请求锁的话,在线程外部无法打断该线程,但是使用lockInterruptibly()允许线程在外部被打断。
执行结果:
线程被打断后捕捉到 InterruptedException 异常,而上图是以输出“Interrupted!”字符串的形式提现,图中的异常并不是被打断的异常,而是未获取锁的情况下执行lock.unlock()抛出的异常。
公平锁指的是,哪个线程等待时间长就优先获得锁。synchronized属于非公平锁,这意味着锁被释放后其他线程会随机获得synchronized锁。
ReentrantLock可以实现公平锁的需求,例如下面代码所示:
public class FairLockDemo extends Thread {
/** 参数为true为公平锁 */
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
FairLockDemo demo = new FairLockDemo();
Thread th1 = new Thread(demo);
Thread th2 = new Thread(demo);
th1.start();
th2.start();
}
}
执行结果:
1、ReentrantLock是Lock接口经常用到的实现类,它可以代替synchronized实现相同的功能,但是需要手动调用 lock.unlock()释放锁。这也是与synchronized的区别最大的区别。
2、手动锁在使用上更加灵活,操作性更强一些。可以尝试使用 tryLock() 和 lockInterruptibly() 等操作自定义锁的操作。
3、ReentrantLock可以实现公平锁,但是在性能上会有些折扣。
B站的资源分享:《马士兵老师高并发编程系列》