ReentrantLock和synchronized一样,都是可重入的,但是它与synchronized是有区别的,在了解两者的区别之前,先尝试着,使用ReentrantLock替换synchronized。
先看下面synchronized的代码:
public class T016 {
synchronized void m1() {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(i);
}
}
synchronized void m2() {
System.out.println("m2...");
}
public static void main(String[] args) {
T016 t016 = new T016();
new Thread(t016::m1, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(t016::m2, "t2").start();
}
}
m1和m2都是同步方法,因此t2需要等待t1执行完毕获得t016对象的锁之后才能开始执行。
上面这段代码,如果用ReentrantLock代替synchronized,如下:
public class T017 {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for(int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(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) {
T017 t017 = new T017();
new Thread(t017::m1, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(t017::m2, "t2").start();
}
}
上面代码中,当需要执行某段同步代码的时候,需要先锁定,即调用ReentrantLock对象的lock方法手动上锁,lock.lock()
相当于synchronized(this)
。在同步代码执行结束时,必须要必须要必须要手动释放锁,即调用ReentrantLock对象的unlock方法。
使用synchronized如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放。
到目前为止讲的是使用ReentrantLock替代synchronized,关于这两者的区别,有如下几点:
1、 使用用ReentrantLock可以进行“尝试锁定” tryLock,这样在无法锁定或者在指定时间内无法锁定,线程可以决定是否继续等待。
看下面代码:
public class T018 {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for(int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void m2() {
boolean isLock = false;
try {
isLock = lock.tryLock(5, TimeUnit.SECONDS);
System.out.println("m2...");
} catch (Exception e) {
e.printStackTrace();
} finally {
if(isLock) {
lock.unlock();
}
}
}
public static void main(String[] args) {
T018 t018 = new T018();
new Thread(t018::m1, "t1").start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(t018::m2, "t2").start();
}
}
m2方法中lock.tryLock(5, TimeUnit.SECONDS)
表示进行尝试锁定,如果无法立即锁定,则等待,等待时间为5秒,tryLock的时候,如果不传参数,则不需要等待,tryLock执行结束,会返回一个布尔值,表示是否已锁定,用一个boolean变量isLock保存起来,在释放锁的时候,先判断isLock是否为true,是则释放锁,如果不进行这个判断,在tryLock没有成功锁定的情况下,调用unlock会抛出IllegalMonitorStateException异常,这个细节要注意下。
2、使用Reentrantlock可以调用lockInterruptibly方法,对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断。
看下面代码:
public class T019 {
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("t1 interrupted!!");
} finally {
lock.unlock();
}
});
t1.start();
Thread t2 = new Thread(()->{
boolean isLock = false;
try {
lock.lockInterruptibly();
isLock = true;
System.out.println("t2 start...");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end.");
} catch (InterruptedException e) {
System.out.println("t2 interrupted!!");
} finally {
if(isLock) {
lock.unlock();
}
}
});
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打断线程2的等待
t2.interrupt();
}
}
t1线程睡眠Integer.MAX_VALUE秒,基本睡死了,因此t2线程很难等到锁,这种情况下,我们可能不希望t2线程继续空等,需要在某个时刻打断t2线程的等待,使用t2.interrupt()
方法就可以办到,但要注意,在t2线程中,调用的锁定方法必须是lock.lockInterruptibly()
而不是常规的lock.lock()
。运行代码,打印结果如下:
t1 start...
t2 interrupted!!
3、Reentrantlock可以指定为公平锁
synchronized是非公平锁,而Reentrantlock是公平锁。
public class T020 extends Thread {
public static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
for(int i = 0; i < 5; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "获得锁");
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
T020 t020 = new T020();
Thread t1 = new Thread(t020);
Thread t2 = new Thread(t020);
t1.start();
t2.start();
}
}
创建Reentrantlock对象的时候,ReentrantLock lock = new ReentrantLock(true)
传入true表示要创建的是公平锁,如果确实是公平的,那么,上面代码执行起来,t1和t2应该交替得到锁,而不是一方占用另一方等待,运行结果如下:
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
这个结果证明了我们创建的锁是公平锁。