zoukankan      html  css  js  c++  java
  • ReentrantLock

    在了解ReentrantLock之前,我们首先回忆一下synchronized,synchronized是java内置的关键字,锁的获取和释放都是由jvm实现,因此用户就不需要显示的去释放锁,是一种独占的加锁方式,但是虽然方便,也有一定的弊端:

    • 1.当线程尝试获取锁的时候,如果获取不到锁会一直阻塞,这个阻塞的过程,用户无法控制
    • 2.如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待

        接下来我们还需要了解几个相关的概念:

    • 可重入锁:可重入锁是指同一个线程可以多次获得同一把锁;ReentrantLock和关键字Synchronized都是可重入锁
    • 可中断锁:可中断锁时只线程在获取锁的过程中,是否可以相应线程中断操作。synchronized是不可中断的ReentrantLock是可中断的
    • 公平锁和非公平锁:公平锁是指多个线程尝试获取同一把锁的时候,获取锁的顺序按照线程到达的先后顺序获取,而不是随机插队的方式获取。synchronized是非公平锁,而ReentrantLock是两种都可以实现,不过默认是非公平锁

    1.ReentrantLock的使用方式

    public class Demo {
        ReentrantLock lock = new ReentrantLock();
        public static int num = 0;
    
        public void add(){
            lock.lock();
            try{
                num++;
            }finally {
                lock.unlock();
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            Demo demo = new Demo();
    
            Thread t1 = new Thread(()->{
                for(int i=0;i<1000;i++){
                    demo.add();
                }
            });
    
            Thread t2 = new Thread(()->{
                for(int i=0;i<1000;i++){
                    demo.add();
                }
            });
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
            System.out.println(demo.num);
        }
    }
    
    

    使用过程:

    • 创建锁:ReentrantLock lock = new ReentrantLock();
    • 获取锁:lock.lock()
    • 释放锁:lock.unlock();
      注意释放锁要放在finally代码块中,这样能保证无论如何线程都会释放锁。

    2.ReentrantLock是可重入锁

    public class Demo {
        ReentrantLock lock = new ReentrantLock();
        public static int num = 0;
    
        public void add(){
            lock.lock();
            lock.lock();
            try{
                num++;
            }finally {
                lock.unlock();
                lock.unlock();
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            Demo demo = new Demo();
    
            Thread t1 = new Thread(()->{
                for(int i=0;i<1000;i++){
                    demo.add();
                }
            });
    
            Thread t2 = new Thread(()->{
                for(int i=0;i<1000;i++){
                    demo.add();
                }
            });
    
            t1.start();
            t2.start();
    
            t1.join();
            t2.join();
            System.out.println(demo.num);
        }
    }
    
    

        在这里lock.lock();执行了两遍,第一次执行获取到了锁,第二次同样可以执行,所以一个线程可以多次获取同一把锁,说明ReentrantLock是可重入锁,不过在这里需要注意,获取了多少次锁,就要释放多少次锁,否则,该线程还是一直占有着该把锁,其它线程仍然会一直阻塞。因此lock()unlock()一定要是成对出现的。

    2.ReentrantLock与公平锁

        大多数情况下都是非公平锁,ReentrantLock的默认构造方法是非公平锁。如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是先来的就先获取到锁。

    根据源码可以知道,构造方法时加上一个true就可以创建公平锁

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

    公平锁测试:

    public class FairLock {
    
        ReentrantLock lock = new ReentrantLock(true);
    
        public void add(String str){
            lock.lock();
            try{
                System.out.println(str);
            }finally {
                lock.unlock();
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            FairLock fairlock = new FairLock();
    
            Thread t1 = new Thread(()->{
                for(int i=0;i<3;i++){
                    fairlock.add("T1获取到锁");
                }
            });
    
            Thread t2 = new Thread(()->{
                for(int i=0;i<3;i++){
                    fairlock.add("T2获取到锁");
                }
            });
    
            Thread t3 = new Thread(()->{
                for(int i=0;i<3;i++){
                    fairlock.add("T3获取到锁");
                }
            });
    
            Thread t4 = new Thread(()->{
                for(int i=0;i<3;i++){
                    fairlock.add("T4获取到锁");
                }
            });
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    

    结果:

    T2获取到锁
    T1获取到锁
    T3获取到锁
    T4获取到锁
    T2获取到锁
    T1获取到锁
    T3获取到锁
    T4获取到锁
    T2获取到锁
    T1获取到锁
    T3获取到锁
    T4获取到锁
    

    可以看到获取锁的顺序都是:2,1,3,4,说明锁时按照先后顺序获得的。

    如果改成非公平锁:把true改为false;
    那么结果如下

    T1获取到锁
    T3获取到锁
    T3获取到锁
    T3获取到锁
    T2获取到锁
    T2获取到锁
    T2获取到锁
    T1获取到锁
    T1获取到锁
    T4获取到锁
    T4获取到锁
    T4获取到锁
    

    可以看到t3可能会连续获得锁,结果是比较随机的,不公平的。为什么会出现线程连续获得锁的情况呢?当一个线程请求锁时,只要获取了同步状态即成功获取锁,在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。

    4.ReentrantLock获取锁的过程是可中断的

        对于synchronized关键字,如果一个线程在等待获取锁,最终只有2种结果:

    • 1.要么获取到锁然后继续后面的操作
    • 2.要么一直等待,直到其他线程释放锁为止

        而ReentrantLock提供了另外一种可能,就是在等的获取锁的过程中(发起获取锁请求到还未获取到锁这段时间内)是可以被中断的,也就是说在等待锁的过程中,程序可以根据需要取消获取锁的请求。有些使用这个操作是非常有必要的。比如:你和好朋友越好一起去打球,如果你等了半小时朋友还没到,突然你接到一个电话,朋友由于突发状况,不能来了,那么你一定达到回府。中断操作正是提供了一套类似的机制,如果一个线程正在等待获取锁,那么它依然可以收到一个通知,被告知无需等待,可以停止工作了。

    public class InterruptLockDemo extends Thread{
    
        private static ReentrantLock lock1 = new ReentrantLock();
        private static ReentrantLock lock2 = new ReentrantLock();
    
        private int threadNum;
    
        private String name;
    
        public InterruptLockDemo(String name,int threadNum){
            super(name);
            this.name = name;
            this.threadNum = threadNum;
        }
    
    
        @Override
        public void run() {
            try{
                if(threadNum==1){
                    lock1.lockInterruptibly();
                    TimeUnit.SECONDS.sleep(1);
                    lock2.lockInterruptibly();
                }
                else{
                    lock2.lockInterruptibly();
                    TimeUnit.SECONDS.sleep(1);
                    lock1.lockInterruptibly();
                }
            }catch (InterruptedException e){
                System.out.println("线程"+name+"被中断,中断标志位:"+this.isInterrupted());
                e.printStackTrace();
            }finally {
                // 如果lock1被当前线程拥有,就释放
                if(lock1.isHeldByCurrentThread()){
                    lock1.unlock();
                }
                if(lock2.isHeldByCurrentThread()){
                    lock2.unlock();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new InterruptLockDemo("t1",1);
            Thread t2 = new InterruptLockDemo("t2",2);
    
            t1.start();
            t2.start();
        }
    

        运行如上代码会产生死锁,lock1被线程t1占用,lock2倍线程t2占用,线程t1在等待获取lock2,线程t2在等待获取lock1,都在相互等待获取对方持有的锁,最终产生了死锁,如果是在synchronized关键字情况下发生了死锁现象,程序是无法结束的。使用jps和jstack,可以查看到如下信息:

    Found one Java-level deadlock:
    =============================
    "t2":
      waiting for ownable synchronizer 0x000000076b424700, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
      which is held by "t1"
    "t1":
      waiting for ownable synchronizer 0x000000076b424730, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
      which is held by "t2"
    
    

    我们对上面代码改造一下,线程t2一直无法获取到lock1,那么等待5秒之后,我们中断获取锁的操作。主要修改一下main方法,如下:

        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new InterruptLockDemo("t1",1);
            Thread t2 = new InterruptLockDemo("t2",2);
    
            t1.start();
            t2.start();
    
            // 等待5秒后中断t2线程
            TimeUnit.SECONDS.sleep(5);
            t2.interrupt();
    
        }
    

    线程t2响应中断请求,然后释放锁

    线程t2被中断,中断标志位:false
    java.lang.InterruptedException
    	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
    	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
    	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    	at ReentrantLock.InterruptLockDemo.run(InterruptLockDemo.java:33)
    

    为什么中断标志位是false?t2线程调用了interrupt()方法,将线程的中断标志置为true,线程发送中断信号触发InterruptedException异常之后,中断标志将被清空。因此中断标志位变化是:false -> true -> false

    • 关于获取锁的过程中被中断,注意几点:
    1. ReentrankLock中必须使用实例方法lockInterruptibly()获取锁时,在线程调用interrupt()方法之后,才会引发 InterruptedException异常
    2. 线程调用interrupt()之后,线程的中断标志会被置为true
    3. 触发InterruptedException异常之后,线程的中断标志有会被清空,即置为false
    4. 所以当线程调用interrupt()引发InterruptedException异常,中断标志的变化是:false->true->false

    5.lock,tryLock,lockInterruptibly区别

    • lock:拿不到锁就会一直阻塞。
    • tryLock:尝试获取锁,如果获取到了锁,就返回true,没有拿到就返回false。如果是带有时间的tryLock,那么没有拿到锁,就先等一段时候,超时之后还未拿到锁,就返回false。
    • lockInterruptibly:lockInterruptibly和lock一样,没有拿到锁,就被阻塞,但是如果此时有interrupt,则“此线程会被唤醒并被要求处理InterruptedException”,并且如果线程已经被interrupt,再使用lockInterruptibly的时候,此线程也会被要求处理interruptedException

    获取锁的4种方法对比:

    总结

    1. ReentrantLock可以实现公平锁和非公平锁
    2. ReentrantLock默认实现的是非公平锁
    3. ReentrantLock的获取锁和释放锁必须成对出现,锁了几次,也要释放几次
      释放锁的操作必须放在finally中执行
    4. lockInterruptibly()实例方法可以相应线程的中断方法,调用线程的interrupt()方法时,lockInterruptibly()方法会触发 InterruptedException异常
    5. 关于 InterruptedException异常说一下,看到方法声明上带有 throwsInterruptedException,表示该方法可以相应线程中断,调用线程的interrupt()方法时,这些方法会触发 InterruptedException异常,触发InterruptedException时,线程的中断中断状态会被清除。所以如果程序由于调用 interrupt()方法而触发 InterruptedException异常,线程的标志由默认的false变为ture,然后又变为false
    6. 实例方法tryLock()获会尝试获取锁,会立即返回,返回值表示是否获取成功
    7. 实例方法tryLock(long timeout, TimeUnit unit)会在指定的时间内尝试获取锁,指定的时间内是否能够获取锁,都会返回,返回值表示是否获取锁成功,该方法会响应线程的中断

    附上ReentrantLock源码——获取公平锁、非公平锁、释放锁:

    https://www.jianshu.com/p/259d076ada14

    原文链接:

    https://blog.csdn.net/u013851082/article/details/70140223
    https://mp.weixin.qq.com/s/gm-EQp_dP7fKHe4-AzLX0g

  • 相关阅读:
    增删改查(Statement接口、PreparedStatement接口、DBUtils(QueryRunner类))
    JDBC(获取数据库连接、自定义JDBC工具类)
    Mysql(视图)
    mysql:dml(元组的插入、删除、修改)
    C++ 指针和引用
    C++ 内存管理
    C++ new/malloc、delete/free
    C++ 内存对齐
    图形渲染原理
    C++获取单链表的倒数第k个节点
  • 原文地址:https://www.cnblogs.com/smallfa/p/13891268.html
Copyright © 2011-2022 走看看