美文网首页
ReentrantLock重入锁

ReentrantLock重入锁

作者: 12点前睡觉hhh | 来源:发表于2019-05-30 13:46 被阅读0次
    public class ReenterLock implements Runnable {
        public static ReentrantLock lock=new ReentrantLock();//声明锁对象
        public static int i=0;
        @Override
        public void run() {
            lock.lock();//加锁
            try{
                for (int j=0;j<10000;j++){
                    i++;
                }
            }finally {
                lock.unlock();//解锁
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ReenterLock t=new ReenterLock();
            Thread t1=new Thread(t);
            Thread t2=new Thread(t);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    }
    

    上述代码便是对重入锁的简单使用。首先声明一个ReentrantLock。通过Reentrant.lock方法加锁,ReentrantLock.unlock解锁。
    对于重入锁,一个线程连续两次获得同一把锁是允许的。
    除了以上的灵活性,重入锁还有一些高级功能。

    1.中断响应

    对于synchronized来说,如果一个线程在等待锁,那么只有两种情况:一是获得这把锁继续执行,二是保持等待。而重入锁则提供了另外一种可能,那就是线程可以被中断。也就是在等待锁的过程中,程序可以根据需求取消对锁的请求。这种功能对于处理死锁有一定的帮助。
    使用实例:

    class InterruptTest implements Runnable{
        public static ReentrantLock lock1=new ReentrantLock();
        public static ReentrantLock lock2=new ReentrantLock();
        int i;
        public InterruptTest(int i){
            this.i=i;
        }
    
        @Override
        public void run() {
            try{
                if (i==1){
                    lock1.lockInterruptibly();
                    System.out.println(i+"正在持有lock1");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {}
                    lock2.lockInterruptibly();
                    System.out.println(i+"正在持有lock2");
    
                }else {
                    lock2.lockInterruptibly();
                    System.out.println(i+"正在持有lock2");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {}
                    lock1.lockInterruptibly();
                    System.out.println(i+"正在持有lock1");
    
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                if(lock1.isHeldByCurrentThread()){
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()){
                    lock2.unlock();
                }
                System.out.println(i+"线程退出了");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            InterruptTest t1=new InterruptTest(1);
            InterruptTest t2=new InterruptTest(2);
            Thread thread1=new Thread(t1);
            Thread thread2=new Thread(t2);
            thread1.start();
            thread2.start();
            Thread.sleep(1000);
            //如果此时同时中断thread1和thread2线程,则产生死锁
             thread2.interrupt();
        }
    }
    

    执行结果:


    使用ReentrantLock.lockInterruptibly();方法进行响应中断的加锁。在main线程中使 thread2.interrupt();则thread2放弃对lock1的申请,直接退出。从而使Thread1获得lock2继续执行。

    2.锁申请等待限时

    除了等待外部通知之外,要避免死锁还有另外一种方法,那就是限时等待,通常,我们无法判断为什么一个线程迟迟拿不到锁。也许是因为死锁,也许是因为饥饿。如果给定一个等待时间,让线程自动放弃,那么对系统来说是有意义的。我们可以使用ReentrantLock.tryLock方法进行一个限时等待。

    class TimeOut implements Runnable{
        public static ReentrantLock lock=new ReentrantLock();
        int i;
        public TimeOut(int i){
            this.i=i;
        }
        @Override
        public void run() {
            try {
                //tryLock方法也可以不带参数,但是如果没有参数的话,则该线程如何能够申请到锁则继续执行,如果不能申请到锁则不会等待,立即退出。
                if (lock.tryLock(5, TimeUnit.SECONDS)){
                    Thread.sleep(6000);
                }else {
                    System.out.println(i+"线程没有请求到锁超时退出");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (lock.isHeldByCurrentThread()){
                    lock.unlock();
                }
            }
        }
    
        /**
         * 本例中,两个线程中可能因为在5秒内无法拿到锁而请求失败,退出。
         * @param args
         */
        public static void main(String[] args) {
            TimeOut timeOut1=new TimeOut(1);
            TimeOut timeOut2=new TimeOut(2);
            Thread t1=new Thread(timeOut1);
            Thread t2=new Thread(timeOut2);
            t1.start();
            t2.start();
    
        }
    }
    

    tryLock方法也可以不带参数直接运行。这种情况下,当前线程会尝试获得锁,如果锁没有被占用,那么申请锁成功,返回true。如果锁被其它线程占用,那么不会等待,返回false。

    3.公平锁

    在多数情况下,锁的申请都是非公平的。而公平锁,会按照时间顺序,先到先得,后到后得。公平锁的一大特点是:不会产生饥饿。

    重入锁的构造函数:
    public ReentrantLock(boolean fair)

    当fair为ture时,表示锁是公平的。公平锁需要系统维护一个有序队列,因此公平锁实现成本高,但是性能低下。因此默认情况下,锁都是非公平的。如果没有特别需要,不会使用公平锁。

    总结

    ReentrantLock几个重要方法:
    lock:获得锁,如果锁已经被占用则等待
    lock Interruptibly:获得锁,但优先响应中断。
    tryLock():尝试获得锁,有参则设置等待时长,如果在时间段内获得锁,则继续执行,如果没有获得则返回false;无参则不等待,如果可以获得则返回true,否则返回false。
    unlock:释放锁

    在重入锁的实现中,主要包含三个要素:
    1.原子状态。原子状态使用CAS操作来存储当前锁的状态,来判断锁是否已经被别的线程占用。
    2.等待队列。所有没有请求到锁的线程,会进入一个等待队列进行等待。待有线程释放锁后,系统能从等待队列中唤醒一个,继续工作。
    3.阻塞原语park和unpark,用来挂起和恢复线程。没有得到锁的线程将被挂起。

    相关文章

      网友评论

          本文标题:ReentrantLock重入锁

          本文链接:https://www.haomeiwen.com/subject/hhbmtctx.html