【JUC基础】04. Lock锁

简介: java.util.concurrent.locks为锁定和等待条件提供一个框架的接口和类,说白了就是锁所在的包。

1、前言

java.util.concurrent.locks为锁定和等待条件提供一个框架的接口和类,说白了就是锁所在的包。

image.png

2、什么是Lock

Lock是一种锁机制,比同步块(synchronized block)更加灵活,同时也更加复杂的线程同步机制。在JDK1.5就已存在Lock接口了。

image.png

其中有三个实现类:

image.png

    1. ReentrantLock:可重入锁
    2. ReentrantReadWriteLock.ReadLock:读锁
    3. ReentrantReadWriteLock.WriteLock:写锁

    3、Lock的API

    Lock接口只提供了6个方法:

    image.png

    其中lock()和unlock()是配对的。lock()是加锁,而unlock()是解锁。lockInterruptibly()与lock()类似,区别在于lock()如果无法获取到锁,线程一直被阻塞,直到锁释放。而lockInterruptibly()允许线程被中断,并抛出java.lang.InterruptedException。

    tryLock()只有在调用时才可以获得锁。如果可用,则获取锁定,并返回true,反之返回false。相应的还有重载方法tryLock(long time, TimeUnit unit)表示在给定的等待时间内空闲,则可以获取锁。如果到了超时时间,还没获取到就放弃获取。

    4、ReentrantLock的基本使用

    Lock是一个接口,官方文档其实也给了如何使用的说明:

      1. 声明Lock对象。Lock lock = new XXXLock();
      2. 方法执行加锁lock.lock();
      3. 在方法块执行完毕后,需要释放锁:lock.unlock();

      image.png

      由于LocK是一个接口,需要使用具体的实现。典型的实现如ReentrantLock。示例代码如下:

      public class ReentrantLockDemo {
          public static void main(String[] args) {
              Phones phones = new Phones();
              new Thread(() -> {
                  for(int i = 0; i< 50; i++) {
                      phones.sale();
                  }
              }, "销售员小王").start();
              new Thread(() -> {
                  for(int i = 0; i< 50; i++) {
                      phones.sale();
                  }
              }, "销售员小红").start();
          }
      }
      class Phones {
          // 库存10部手机
          private int total = 50;
          // 1、 声明锁的实例
          ReentrantLock lock = new ReentrantLock();
          public void sale(){
              try {
                  // 2、加锁,代码规约检测会提示你加载try的第一行
                  lock.lock();
                  if(total > 0){
                      System.out.println(Thread.currentThread().getName() + "卖出了一部手机,当前库存剩余:" + (--total));
                  }
              } finally {
                  // 3、释放锁
                  lock.unlock();
              }
          }
      }

      image.gif

      执行结果:
      image.png

      ReentrantLock是个可重入锁,其中可以执行公平锁和非公平锁。如new ReentrantLock(true),默认是非公平锁(false)。

      4.1、公平锁和非公平锁

        • 公平锁:每个线程获取锁的顺序按照先后顺序获取。关键字眼:先到先得。
        • 优点:能保证所有的线程都得到资源,不会产生线程饥饿现象。
        • 缺点:吞吐量低,除了第一个线程以外,其余的都处于排队阻塞的状态,cpu需要每次唤醒线程,开销较大。
        • 非公平锁:多个线程同时尝试获取,哪个线程优先获取到锁取决于系统分配策略。关键字眼:无需排队。
        • 优点:吞吐量高,cpu无需唤醒所有的线程,开销低。
        • 缺点:会产生线程饥饿现象,可能后到的线程先获取到锁,而前面的线程永远都获取不到

          5、读写锁ReadWriteLock

          ReadWriteLock是一个读写锁接口。什么是读写锁呢?看下官方文档说明:

          image.png

          ReadWriteLock分为一个读锁和一个写锁。读锁可以被多个线程持有,而写锁只能被一个线程持有。典型的实现类有ReentrantReadWriteLock。

          读锁:就是我们常说的共享锁。

          写锁:就是常说的独占锁。

          示例代码:

          public class ReadWriteLock {
              public static void main(String[] args) {
                  MyMap map = new MyMap();
                  // 写操作
                  for (int i = 0; i < 5; i++) {
                      int finalI = i;
                      new Thread(() -> map.put(String.valueOf(finalI), String.valueOf(finalI))).start();
                  }
                  // 读操作
                  for (int i = 0; i < 5; i++) {
                      int finalI = i;
                      new Thread(() -> map.get(String.valueOf(finalI))).start();
                  }
              }
          }
          // 模拟公共资源类
          class MyMap extends HashMap<String, String> {
              @Override
              public String get(Object key) {
                  System.out.println(Thread.currentThread().getName() + "获取key:" + key);
                  return super.get(key);
              }
              @Override
              public String put(String key, String value) {
                  System.out.println(Thread.currentThread().getName() + "写入key:" + key);
                  String put = super.put(key, value);
                  System.out.println(Thread.currentThread().getName() + "写入key:" + key + "完成");
                  return put;
              }
          }

          image.gif

          如果不加入任何的锁限制,我们直到结果肯定是很随机的。在写入操作时会被其他读线程插队。

          image.png

          加入读写锁后:

          public class ReadWriteLock {
              public static void main(String[] args) {
                  MyMap map = new MyMap();
                  // 写操作
                  for (int i = 0; i < 5; i++) {
                      int finalI = i;
                      new Thread(() -> map.put(String.valueOf(finalI), String.valueOf(finalI))).start();
                  }
                  // 读操作
                  for (int i = 0; i < 5; i++) {
                      int finalI = i;
                      new Thread(() -> map.get(String.valueOf(finalI))).start();
                  }
              }
          }
          // 模拟公共资源类
          class MyMap extends HashMap<String, String> {
              private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
              @Override
              public String get(Object key) {
                  try{
                      rwLock.readLock().lock();
                      System.out.println(Thread.currentThread().getName() + "获取key:" + key);
                      return super.get(key);
                  } finally {
                      rwLock.readLock().unlock();
                  }
              }
              @Override
              public String put(String key, String value) {
                  try {
                      rwLock.writeLock().lock();
                      System.out.println(Thread.currentThread().getName() + "写入key:" + key);
                      String put = super.put(key, value);
                      System.out.println(Thread.currentThread().getName() + "写入key:" + key + "完成");
                      return put;
                  } finally {
                      rwLock.writeLock().unlock();
                  }
              }
          }


          image.png

          我们可以看到在写入的时候,并不会有读的线程插队操作。

          6、小结

          关于Lock锁大概就讲这些,主要讲了ReentrantLock和ReadWriteLock的基本使用,也是通常比较常用的。其中Locks中还有一个接口Condition,这个等后面讲生产者和消费者的时候在细说。

          相关实践学习
          部署Stable Diffusion玩转AI绘画(GPU云服务器)
          本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
          相关文章
          |
          6月前
          |
          监控 安全 Java
          Java中的锁(Lock、重入锁、读写锁、队列同步器、Condition)
          Java中的锁(Lock、重入锁、读写锁、队列同步器、Condition)
          32 0
          |
          Java 程序员 API
          【Lock锁的使用与原理】
          【Lock锁的使用与原理】
          199 0
          |
          算法 Java
          JUC--锁
          简单介绍锁
          |
          算法 调度
          JUC基础(三)—— Lock锁 及 AQS(1)
          JUC基础(三)—— Lock锁 及 AQS
          133 0
          |
          Java
          JUC基础(三)—— Lock锁 及 AQS(2)
          JUC基础(三)—— Lock锁 及 AQS
          96 0
          |
          安全 Java
          Java并发编程之Lock(同步锁、死锁)
          这篇文章是接着我上一篇文章来的。
          130 0
          |
          安全 Java
          多线程详解p18、Lock锁
          多线程详解p18、Lock锁
          |
          安全 Java 调度
          多线程同步问题,锁Lock,synchronized
          线程同步机制 并发:同一个对象被多个线程同时操作 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个县城再使用 线程同步形成条件:队列+