ReentrantLock简单理解

一、什么是ReentrantLock

ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的一种手段,它的功能类似与Synchronized,但是又不等于Synchronized,是一种互斥锁

1.1 ReentrantLock与Synchronized的区别

  • synchronized是JVM层次的锁,reentrantLock是jdk层次的锁
  • synchronized的锁状态是无法在代码中直接判断的,但是reentrantLock可以通过ReentrantLock.isLocked()判断
  • synchronized是非公平锁,reentrantLock可以是公平也可以是非公平
  • synchronized是不可以被中断的,而reentrantLock.lockInterruptibly方法是可以中断的
  • 在发生异常时,synchronized会自动释放锁,而reentrantLock需要开发者在finally块显示释放
  • reentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待时长的获取,更加灵活
  • synchronized在特定情况下对于已经在等待的线程,是后来的线程获取锁,而reentrantLock对于已经在等待的线程,是先来的线程获取锁

1.2 reentrantLock比较Sysnchronized具有一下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与synchronized一样,都支持可重入

二、ReentrantLock使用

2.1 常用的方法

  • void lock():获取锁,如果锁不可用,则当前线程将因线程调度而禁用,并处于休眠状态
  • void lockInterruptibly:当前线程被中断,获取锁,如果获取到了,并立即返回,如果没有获取到,则当前线程将因线程调度而禁用,并处于休眠状态,直到锁被当前线程获取(获取锁正常返回),或者其他线程中断当前线程,并支持当前线程在获取锁时中断,然后抛出InterruptedException并清除当前线程状态(意思是睡眠时其他线程中断了当前线程获取锁直接清除当前线程睡眠状态)
  • boolean tryLock():只有在调用它是空闲时才能获取到锁(锁可能拿不到,但是lock一定能拿到),如果获取到锁(可用),就返回true,否则就返回false。
  • boolean tryLock(long time,TimeUnit unit);tryLock重载方法。如果锁在给定的等待时间内空闲并且当前线程没有中断,则获取该锁。如果指定的等待时间为false,则返回值为false。如果时间小于零,则该方法根本不会等待,time:等待锁定的最长时间,unit:时间参数单位
  • void unlock():释放锁。
  • Condition newCondtion():返回绑定到此Lock实例的新条件实例。在等待条件之前,锁必须由当前线程持有。告诉Condtion.await()将等待之前自动释放锁,并在等待返回之前重新获取锁。

2.2 基本用法

private static ReentrantLock reentrantLock = new ReentrantLock();
private void test1() {
   //加锁,如果获取不到锁,就一直等待,直到获取到锁
   reentrantLock.lock();
   try {
     System.out.println("业务逻辑开始执行");
     Thread.sleep(3000);
     System.out.println("业务逻辑花了三秒执行完毕");
   } catch (InterruptedException e) {
      e.printStackTrace();
   } finally {
      reentrantLock.unlock();
      System.out.println("释放锁,并且unlock()要放在最上面");
   }
}
 public static void main(String[] args) {
    ReentrantLockTest test = new ReentrantLockTest();
    test.test1();
}

2.3 可重入锁

可重入锁是指同一个线程如果首次获得这把锁,那么因为它是这把锁的拥有者,因此有权利再次获得这把锁。如果是不可重入锁,那么第二次获得这把锁时,自己也会被锁住。

private static void test2() {
        reentrantLock.lock();
        try {
            System.out.println("执行有锁的test2方法.......");
            test3();
        } catch (Exception e) {

        } finally {
            reentrantLock.unlock();
        }
    }

private static void test3() {
        System.out.println("普通方法test3......");
    }

private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) {
        reentrantLock.lock();
        try {
            System.out.println("开始执行业务逻辑");
            test2();
            System.out.println("业务逻辑执行结束");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }

    }

2.4 可中断

synchronized和reentrantLock.lock()的锁,是不可被打断的,也就是说,别的线程已经获得了锁,线程就需要一直等待下去,不能中断,直到获得到锁才能运行

通过reenttrantLock.lockInterruptibly();可以通过调用阻塞线程的interrupt方法打断

private static void test6() throws InterruptedException  {
        new Thread(()->{
            reentrantLock.lock();
            try {
                System.out.println("t2获取锁以后开始执行业务逻辑");
                Thread.sleep(2000);
                System.out.println("t2两秒以后线程起来");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }

        },"t2").start();

            //确保t2先拿到锁
        Thread.sleep(2000);

        Thread t1 = new Thread(()->{
            try {
                reentrantLock.lockInterruptibly();
                System.out.println("t1获取到锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("t1被打断......");
            }finally {
                reentrantLock.unlock();
            }
        },"t1");
        t1.start();
        System.out.println("打断t1");
        t1.interrupt();

    }

2.5 可设置超时时间

使用lock.trylock()方法会返回获取锁是否成功,如果成功,则返回true,反正则返回false。并且trylock方法可以设置指定的等待时间,在获取锁的过程中,如果等待时间超时,或者被打断,那么直接从阻塞队列中移除,此时获取锁就失败了,不会一直阻塞(可以用来实现解决死锁问题),如果不设置等待时间,会立即生效。

线程池工具类:线程池工具类_java-zh的博客-CSDN博客


private static ReentrantLock reentrantLock = new ReentrantLock();

public static void main(String[] args) {
        test5();
    }
private static void test5() {
        ThreadPoolUtils.execute(() -> {
            System.out.println("开始尝试获取锁....");
            System.out.println("是否有获取锁 -> " + reentrantLock.tryLock());
            try {
                if (reentrantLock.tryLock(1, TimeUnit.SECONDS)) {
                    System.out.println("获取锁以后开始执行业务逻辑....");
                } else {
                    System.out.println("设置了一秒,获取不到锁");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });
        //主线程先获取锁
        reentrantLock.lock();
        try {
            System.out.println("主业务逻辑开始执行");
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
            System.out.println("最后释放锁");
        }
    }
主业务逻辑开始执行
最后释放锁
开始尝试获取锁....
是否有获取锁 -> true
获取锁以后开始执行业务逻辑....

        

2.6 公平锁和非公平锁

  • 公平锁:多个线程按照申请锁的顺序去获取锁,线程会直接进入队列去排队,永远都是队列的第一位才能获得到锁。
  • 公平锁优点:所有的线程都可以获取到资源,不会饿死在队列中
  • 公平锁缺点:吞吐量会下降很多,队列里面锁了第一个线程,其他线程都会阻塞,cpu唤醒阻塞线程的开销会很大
  • 非公平锁:多个线程去尝试锁的时候,会直接尝试获取,如果获取不到,再返回队列等待,如果能获取到,直接获取锁
  • 非公平锁优点:可以减少CPU唤醒线程的开销,整体吞吐效率会高一点,CPU也不必去唤醒所有线程,会减少唤醒线程数量
  • 非公平锁缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁
//非公平锁(无参默认是非公平锁)
private static ReentrantLock reentrantLock = new ReentrantLock(false);
//公平锁
private static final ReentrantLock reentrantLock = new ReentrantLock(true);


线程2比线程1更先获取到锁

2.7 条件变量Condition

传统对象等待集合只有waitSet,Lock可以通过newCondtion()方法生成多个等待集合Condition对象。Lock和Condition是一对多关系。

2.7.1 使用流程

  • await前需要获得锁
  • await执行后,会释放锁,进入conditionObject(条件变量)中等待
  • await的线程被唤醒(或打断,或超时)去重新竞争lock锁
  • 竞争lock锁成功以后,从await后继续执行
  • signal方法用来唤醒条件变量(等待室)汇总的某一个等待的线程
  • signalAll方法,唤醒条件变量(休息室)中所有线程

public class ConditionTest {
    //ReentrantLock可以设置多个条件变量
    private static boolean hasCar = false;
    private static boolean hasTrain = false;
    private static final ReentrantLock reentrantLock = new ReentrantLock();

    //等待汽车的条件变量
    private static Condition waitCarSet = reentrantLock.newCondition();
    //等待火车条件变量
    private static Condition waitTrainSet = reentrantLock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        //等待汽车
        ThreadPoolUtils.execute(ConditionTest::waitCar);
        //等待火车
        ThreadPoolUtils.execute(ConditionTest::waitTrain);
        Thread.sleep(2000);
        //汽车来了
        ThreadPoolUtils.execute(ConditionTest::car);
        //火车来了
        ThreadPoolUtils.execute(ConditionTest::train);
    }

    private static void waitCar() {
        reentrantLock.lock();
        try {
            System.out.println("汽车来了吗?");
            while (!hasCar) {
                System.out.println("汽车没来,再等等");
                //进入等待汽车亭
                waitCarSet.await();
            }
            System.out.println("汽车来了,开始准备上汽车了!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    private static void waitTrain() {
        reentrantLock.lock();
        try {
            System.out.println("火车到了吗?");
            while (!hasTrain) {
                System.out.println("火车还没有到");
                waitTrainSet.await();
            }
            System.out.println("火车到了,开始准备上火车了!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    private static void train() {
        reentrantLock.lock();
        try {
            System.out.println("火车来了");
            hasTrain = true;
            waitTrainSet.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    private static void car() {
        reentrantLock.lock();
        try {
            System.out.println("汽车来了");
            hasCar = true;
            waitCarSet.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

}

结果

汽车来了吗?
汽车没来,再等等
火车到了吗?
火车还没有到
汽车来了
汽车来了,开始准备上汽车了!
火车来了
火车到了,开始准备上火车了!

2.8 固定顺序运行

public class WaitTest {

    private static final Object lock = new Object();

    private static boolean flagRunnable = false;

    public static void main(String[] args) {
        ThreadPoolUtils.execute(() -> {
            synchronized (lock) {
                while (!flagRunnable) {
                    try {
                        //开始等待,等待的过程会释放锁
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("1");
            }
        });
        ThreadPoolUtils.execute(() -> {
            synchronized (lock) {
                System.out.println("2");
                flagRunnable = true;
                lock.notify();
            }
        });
    }


}

2.9 交替输出

线程1输出1五次,线程2输出2五次,线程3输出3五次,最后结果abcabcabcabcabc

2.9.1 synchronized交替输出

public class JiaoTiTest {
    //需要输出的次数
    private  Integer num = 5;

    public static void main(String[] args) {
        JiaoTi jiaoTi = new JiaoTi(1,5);
        ThreadPoolUtils.execute(()->{
            jiaoTi.t("a",1,2);
        });
        ThreadPoolUtils.execute(()->{
            jiaoTi.t("b",2,3);
        });
        ThreadPoolUtils.execute(()->{
            jiaoTi.t("c",3,1);
        });
    }

}
class JiaoTi{

    //当前的标记
    private Integer flag;

    //循环的次数
    private Integer num;

    public JiaoTi(Integer flag, Integer num) {
        this.flag = flag;
        this.num = num;
    }

    /**
     *
     * @param con 内容
     * @param waitflag 当前标记
     * @param nextFlag 下一个标记
     *  比如当前内容为a 当前标记 1 下一个标记2
     *      当前内容为b 当前标记2  下一个标记3
     *      当前内容为c 当前标记3 下一个标记1
     */
    public  void t(String con, Integer waitflag, Integer nextFlag){
        for (int i = 0; i < num; i++) {
            synchronized (this){
                //如果你要输出的标记跟抢占到资源的标记不一样,那么就进行等待
                while (!flag.equals(waitflag)){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(con);
                //执行完成以后,当前的标记变成了下一个标记
                flag = nextFlag;
                this.notifyAll();
            }
        }

    }
}
abcabcabcabcabc

2.9.2 reentrantLock交替输出

public class JiaoTiTest2 {
    
    private static  ReentrantLock reentrantLock = new ReentrantLock();
    private static Condition condition1 = reentrantLock.newCondition();
    private static Condition condition2 = reentrantLock.newCondition();
    private static Condition condition3 = reentrantLock.newCondition();

    public static void main(String[] args) {
        ThreadPoolUtils.execute(()->{
            noFair("a",condition1,condition2);
        });
        ThreadPoolUtils.execute(()->{
            noFair("b",condition2,condition3);
        });
        ThreadPoolUtils.execute(()->{
            noFair("c",condition3,condition1);
        });
        System.out.print("开始打印:");
        reentrantLock.lock();
        try {
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }

    public static void noFair(String con,Condition condition,Condition nextCondition){
        for (int i = 0; i < 5; i++) {
            reentrantLock.lock();
            try {
                condition.await();
                System.out.print(con);
                nextCondition.signal();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        }
    }


}
开始打印:abcabcabcabcabc

你可能感兴趣的:(java,jvm,开发语言)