Java并发编程:浅谈ReentrantLock类

目录

一、ReentrantLock的重入性

二、公平锁与非公平锁

三、测试代码


在上篇Java并发编程:Lock接口中,我们了解到Lock类有一个唯一的实现类ReentrantLock类。

ReentrantLock重入锁:支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入(可见Java并发编程:线程安全&synchronized关键字)。同时,ReentrantLock还支持公平锁和非公平锁两种方式。

一、ReentrantLock的重入性

重入性可以理解为锁的分配机制:基于线程的分配,而不是基于方法调用的分配。我们可以用synchronized关键字来理解:

class Test {

    public synchronized void method1() {
        method2();
    }
     
    public synchronized void method2() {
         
    }
}

上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生这种状况。

二、公平锁与非公平锁

ReentrantLock支持两种锁:公平锁和非公平锁

  • 公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁。
  • 非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

这么一看synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。

ReentrantLock的构造方法无参时是构造非公平锁:

public ReentrantLock() {
    sync = new NonfairSync();
}

另外还提供了另外一种方式,可传入一个boolean值,true时为公平锁,false时为非公平锁

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁与非公平锁比较:

  • 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。
  • 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

另外在ReentrantLock类中定义了很多方法,比如:

  •  isFair()        //判断锁是否是公平锁
  •  isLocked()    //判断锁是否被任何线程获取了
  •  isHeldByCurrentThread()   //判断锁是否被当前线程获取了
  •  hasQueuedThreads()   //判断是否有线程在等待该锁

在ReentrantReadWriteLock中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过,ReentrantReadWriteLock并未实现Lock接口,它实现的是ReadWriteLock接口。

三、测试代码

1、中断响应

对于synchronized块来说,要么获取到锁执行,要么持续等待。而重入锁的中断响应功能就合理地避免了这样的情况。比如,一个正在等待获取锁的线程被“告知”无须继续等待下去,就可以停止工作了。下面代码演示了使用重入锁来解决死锁:

先使用lock方法上锁

import java.util.concurrent.locks.ReentrantLock;

public class KillDeadlock implements Runnable{
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lockRule;

    public KillDeadlock(int lockRule) {
        this.lockRule = lockRule;
    }

    public void run() {
        try {
            if (lockRule == 1) {
                lock1.lock();      	//通过lock方法给lock1上锁
                if(lock1.isLocked()){    //判断lock1是否上锁了
                	System.out.println(Thread.currentThread().getName()+"'s lock1 is locked");
                }
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName()+" After 0.5s");
                } catch (InterruptedException e) {}
                lock2.lock();
                if(lock2.isLocked()){
                	System.out.println(Thread.currentThread().getName()+"'s lock2 is locked");
                }
            } else {
            	lock2.lock();
                if(lock2.isLocked()){
                	System.out.println(Thread.currentThread().getName()+"'s lock2 is locked");
                }
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getName()+ "After 0.5s");
                } catch (InterruptedException e) {}
                lock1.lock();  
                if(lock1.isLocked()){
                	System.out.println(Thread.currentThread().getName()+"'s lock1 is locked");
                }
            }
        }finally {
            if (lock1.isHeldByCurrentThread()) { 	//判断lock1是否持有线程
            	lock1.unlock();  
            	if(!lock1.isLocked()){
            		System.out.println(Thread.currentThread().getName()+"'s lock1 is unlocked");
            	}
            }
            if (lock2.isHeldByCurrentThread()) {
            	lock2.unlock();  
            	if(!lock2.isLocked()){
            		System.out.println(Thread.currentThread().getName()+"'s lock2 is unlocked");
            	}
            }
            System.err.println(Thread.currentThread().getName() + "结束!");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        KillDeadlock deadLock1 = new KillDeadlock(1);
        KillDeadlock deadLock2 = new KillDeadlock(2);
        Thread t1 = new Thread(deadLock1);
        t1.setName("t1");
        Thread t2 = new Thread(deadLock2);
        t2.setName("t2");
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("After 1s");
        t2.interrupt(); 
    }
}

运行结果:

t1、t2线程开始运行时,会分别持有lock1和lock2而请求去lock1和lock2,这样就发生了死锁,t1和t2一直处于等待状态。由于通过lock上锁不能被interrupt

而此时我们通过lockInterruptibly方法上锁时,结果如下:

可见t2.interrup时即t2线程状态标记为中断后,持有重入锁lock2的线程t2会响应中断,并不再继续等待lock1,同时释放了其原本持有的lock2,这样t1获取到了lock2,正常执行完成。t2也会退出,但只是释放了资源并没有完成工作。

2、锁申请等待限时

 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。

  • tryLock():当线程尝试获取锁时,如果获取到锁则继续执行,如果锁被其他线程持有,则立即返回 false ,也就是不会使当前线程等待,所以不会产生死锁
  • tryLock(long timeout, TimeUtil unit) :在指定时长内获取到锁则继续执行,如果等待指定时长后还没有获取到锁则返回false
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TryLockTest implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();

    public void run() {        
       try {
		if (lock.tryLock(1, TimeUnit.SECONDS)) { // 在1秒内尝试上锁
		    System.out.println(Thread.currentThread().getName()+"'s lock is locked");
		    Thread.sleep(2000);  //休眠2秒
		} else {
		    System.err.println(Thread.currentThread().getName() + "获取锁失败!");
		}
	} catch (InterruptedException e) {
		    e.printStackTrace();
	}       
    }

    public static void main(String[] args) throws InterruptedException {
        TryLockTest test = new TryLockTest();
        Thread t1 = new Thread(test); 
        t1.setName("t1");
        Thread t2 = new Thread(test); 
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

t1先获取到锁,并休眠2秒,这时t2开始尝试上锁,在1秒后依然没有获取到锁,就不再继续等待。

3、公平锁

公平锁,就是按照时间先后顺序,使先等待的线程先得到锁,而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。

import java.util.concurrent.locks.ReentrantLock;

public class FairLockTest implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();

    public void run() {
        while (true) {
            try {
                lock.lock();
                System.err.println(Thread.currentThread().getName() + "获取到了锁!");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        FairLockTest test = new FairLockTest();
        Thread t1 = new Thread(test, "t1");
        Thread t2 = new Thread(test, "t2");
        t1.start();t2.start();
    }
}

部分运行结果

可见t1和t2交替获取到锁,如果是非公平锁的话就不一定了


参考资料

1、https://juejin.im/post/5aeb0a8b518825673a2066f0

2、https://blog.csdn.net/weixin_39910081/article/details/80147754

3、https://blog.csdn.net/Somhu/article/details/78874634

猜你喜欢

转载自blog.csdn.net/qq_39192827/article/details/86418275
今日推荐