重入锁可以完全替代关键字synchronize。在jdk5.0的早期版本中,重入锁的性能远超于关键字synchronize,但是在jdk6.0开始,jdk在关键字synchronize上做了大量的优化,似的两者间的差距并不是很大。
1.中断响应
对于关键字来说,如果一个线程在等待锁,那么结果只有两种情况,要么获得锁继续执行,要么保持等待。而使用重入锁,则提供了另外一种可能,就是线程可以中断。
代码示例:
package com.gpdi.operatingunit.test;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Lxq
* @date 2020/2/11 11:44
* @describtion
**/
public class IntLock implements Runnable{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
/**
* 加锁控制顺序,方便构成死锁
* @param lock
*/
public IntLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1){
lock1.lockInterruptibly();
try {
Thread.sleep(500);
}catch (InterruptedException e){}
lock2.lockInterruptibly();
}else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
}catch (InterruptedException e){}
lock1.lockInterruptibly();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
// lock.isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定
if (lock1.isHeldByCurrentThread()){
System.out.println("lock1" + lock1.isHeldByCurrentThread());
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()){
System.out.println("lock2" + lock2.isHeldByCurrentThread());
lock2.unlock();
}
System.out.println(Thread.currentThread() + "线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
java.lang.InterruptedException
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:944)
at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1263)
at java.base/java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:317)
at com.gpdi.operatingunit.test.IntLock.run(IntLock.java:40)
at java.base/java.lang.Thread.run(Thread.java:834)
lock2true
lock1true
lock2true
Thread[Thread-1,5,main]线程退出
Thread[Thread-0,5,main]线程退出
线程t1和线程t2启动后,t1先占用lock1,再占用lock2;t2先占用lock2,再请求lock1.因此形成了t1和t2之前互相等待,对锁的请求统一使用lockInterruptibly(),这是一个可以中断响应的锁申请动作,即再等待的过程中,可以响应中断。
再main线程 处于休眠状态,此时,两个线程都处于死锁的状态,当main 线程中t2线程中断,故t2放弃对lock1的申请,同时释放了lock2.这个操作导致t1的线程可以顺利得到lock2而继续执行下去。
2.锁申请等待限时
除了等待外部的通知之外,要避免死锁还用另外一种方法,就是限时等待。如果给定一个时间,让线程自动放弃,那么对于系统来说是有意义的,我们使用tryLock()方法进行一次限时等待。
代码示例:
package com.gpdi.operatingunit.test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Lxq
* @date 2020/2/11 12:19
* @describtion
**/
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
}else {
System.out.println("get lock failed");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
if (lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
public static void main(String[] args) {
TimeLock lock = new TimeLock();
Thread t1 = new Thread(lock);
Thread t2 = new Thread(lock);
t1.start();
t2.start();
}
}
get lock failed
tryLock 接受两个参数,一个表示等待时长,一个表示计时单位,这里的单位是秒,时长为5,表示线程在请求锁的最多时长为5秒,如果超过了5秒还没获取锁,就会返回false,如果成功获取锁,则返回true。
ReentrantLock.tryLock() 方法也可以不带参数直接运行。在这种情况下,当前线程会尝试获取锁,如果锁并未被其他线程占用,则申请锁成功,并返回true。如果锁被其他线程占用,则当前线程不会继续等待,而是立即放回false,这种模式下不会引起线程等待,因此也不会产生死锁。
代码示例:
package com.gpdi.operatingunit.test;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Lxq
* @date 2020/2/11 12:34
* @describtion
**/
public class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
if (lock == 1) {
while (true) {
try {
if (lock1.tryLock()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock2.tryLock()) {
try {
System.out.println(Thread.currentThread() + " my job done");
return;
}finally {
lock2.unlock();
}
}
}
} finally {
lock1.unlock();
}
}
}else {
while (true) {
try {
if (lock2.tryLock()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lock1.tryLock()) {
try {
System.out.println(Thread.currentThread() + " my job done");
return;
}finally {
lock1.unlock();
}
}
}
} finally {
lock2.unlock();
}
}
}
}
public static void main(String[] args) {
TryLock r1 = new TryLock(1);
TryLock r2 = new TryLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
上面代码很容易产生死锁,但是使用了tryLock()方法后,这种情况就大大改善。由于线程不会傻傻等待,而是不停尝试,因此只要有足够长的时间,线程最终会获得所需要的资源,从而正常执行。
3.公平锁
大多数情况下,锁的申请都是非公平的。如果我们使用synchronize关键字进行控制的话,那么产生的锁就是非公平锁。而重入锁允许我们对其公平性进行设置。构造函数如下:
public ReentrantLock(boolean fair) {
this.sync = (ReentrantLock.Sync)(fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
}
当参数为fair为true时,表示锁时公平的。公平锁看起来很优美,但是要实现公平锁必然要维护一个有序的队列,因此公平锁实现的成本比较高,性能却非常低下,在默认的情况下,锁时非公平的。如果没有特殊的要求,则不要使用公平锁。
代码示例:
package com.gpdi.operatingunit.test;
import org.apache.poi.ss.formula.functions.T;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Lxq
* @date 2020/2/11 12:58
* @describtion
**/
public class FairLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获取锁");
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
FairLock r1 = new FairLock();
Thread t1 = new Thread(r1,"Thread_t1");
Thread t2 = new Thread(r1,"Thread_t2");
t1.start();
t2.start();
}
}
总结:
- lock()获取锁,如果锁被占用,则等待。
- lockInterruptibly():获取锁,优先响应中断。
- tryLock()获取锁,如果成功,则返回true,失败返回false。该方法不等待,立即返回。
- tryLock(long time,TimeUnit unit)给定时间内尝试获取锁。
- unlock()释放锁