• 深入理解Java中的锁(二)


    locks包结构层次

    img

    Lock 接口

    方法签名描述
    void lock(); 获取锁(不死不休)
    boolean tryLock(); 获取锁(浅尝辄止)
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 获取锁(过时不候)
    void lockInterruptibly() throws InterruptedException; 获取锁(任人摆布)
    void unlock(); 释放锁
    Condition newCondition();  

    代码示例:

    public class GetLockDemo {
    
      // 公平锁
      // static Lock lock =new ReentrantLock(true);
    
      // 非公平锁
      static Lock lock = new ReentrantLock();
    
      public static void main(String[] args) throws InterruptedException {
        // 主线程 拿到锁
        lock.lock();
    
        Thread thread =
            new Thread(
                () -> {
                  // 子线程 获取锁(不死不休)
                  System.out.println("begain to get lock...");
                  lock.lock();
                  System.out.println("succeed to get lock...");
    
                  //              // 子线程 获取锁(浅尝辄止)
                  //              boolean result = lock.tryLock();
                  //              System.out.println("是否获得到锁:" + result);
                  //
                  //              // 子线程 获取锁(过时不候)
                  //              try {
                  //                boolean result1 = lock.tryLock(5, TimeUnit.SECONDS);
                  //                System.out.println("是否获得到锁:" + result1);
                  //              } catch (InterruptedException e) {
                  //                e.printStackTrace();
                  //              }
                  //
                  //              // 子线程 获取锁(任人摆布)
                  //              try {
                  //                System.out.println("start to get lock Interruptibly");
                  //                lock.lockInterruptibly();
                  //              } catch (InterruptedException e) {
                  //                e.printStackTrace();
                  //                System.out.println("dad asked me to stop...");
                  //              }
    
                });
    
        thread.start();
        Thread.sleep(10000L);
        lock.unlock();
      }
    }
    

    结论:

    • lock() 最常用
    • lockInterruptibly() 方法一般更昂贵,有的实现类可能没有实现 lockInterruptible() 方法。只有真的需要用中断时,才使用,使用前应看清实现类对该方法的描述。

    Condition

    Object中的wait(), notify(), notifyAll()方法是和synchronized配合使用的可以唤醒一个或者多个线程。Condition是需要与Lock配合使用的,提供多个等待集合和更精确的控制(底层是park/unpark机制);

    协作方式死锁方式1 (锁)死锁方式2(先唤醒,再挂起)备注
    suspend/resume 死锁 死锁 弃用
    wait/notify 不死锁 死锁 只用于synchronized关键字
    park/unpark 死锁 不死锁  
    condition 不死锁 死锁  

    condition代码示例:

    public class ConditionDemo {
    
      static Lock lock = new ReentrantLock();
    
      static Condition condition = lock.newCondition();
    
      public static void main(String[] args) throws InterruptedException {
        Thread thread =
            new Thread(
                () -> {
                  lock.lock();
                  System.out.println("condition.await()");
                  try {
                    condition.await();
                    System.out.println("here i am...");
                  } catch (InterruptedException e) {
                    e.printStackTrace();
                  } finally {
                    lock.unlock();
                  }
                });
        thread.start();
    
        Thread.sleep(2000L);
        lock.lock();
    
        condition.signalAll();
    
        lock.unlock();
      }
    }
    

    ReetrantLock

    ReentrantLock是可重入锁,同一线程可以多次获取到锁

    img

    ReentrantLock实现原理分析

    1. ReentrantLock需要一个owner用来标记那个线程获取到了锁,一个count用来记录加锁的次数和一个waiters等待队列用来存放没有抢到锁的线程列表
    2. 当有线程进来时,会先判断count的值,如果count为0说明锁没有被占用
    3. 然后通过CAS操作进行抢锁
    4. 如果抢到锁则count的值会加1,同时将owner设置为当前线程的引用
    5. 如果count不为0同时owner指向当前线程的引用,则将count的值加1
    6. 如果count不为0同时owner指向的不是当前线程的引用,则将线程放入等待队列waiters中
    7. 如果CAS抢锁失败,则将线程放入等待队列waiters中
    8. 当线程使用完锁后,会释放其持有的锁,释放锁时会将count的值减1,如果count值为0则将owner设为null
    9. 如果count值不为0则会唤醒等待队列头部的线程进行抢锁

    手动实现ReentrantLock代码示例:

    public class MyReentrantLock implements Lock {
    
      // 标记重入次数的count值
      private AtomicInteger count = new AtomicInteger(0);
    
      // 锁的拥有者
      private AtomicReference<Thread> owner = new AtomicReference<>();
    
      // 等待队列
      private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();
    
      @Override
      public boolean tryLock() {
        // 判断count是否为0,若count!=0,说明锁被占用
        int ct = count.get();
        if (ct != 0) {
          // 判断锁是否被当前线程占用,若被当前线程占用,做重入操作,count+=1
          if (owner.get() == Thread.currentThread()) {
            count.set(ct + 1);
            return true;
          } else {
            // 若不是当前线程占用,互斥,抢锁失败,return false
            return false;
          }
        } else {
          // 若count=0, 说明锁未被占用,通过CAS(0,1) 来抢锁
          if (count.compareAndSet(ct, ct + 1)) {
            // 若抢锁成功,设置owner为当前线程的引用
            owner.set(Thread.currentThread());
            return true;
          } else {
            return false;
          }
        }
      }
    
      @Override
      public void lock() {
        // 尝试抢锁
        if (!tryLock()) {
          // 如果失败,进入等待队列
          waiters.offer(Thread.currentThread());
    
          // 自旋
          for (; ; ) {
            // 判断是否是队列头部,如果是
            Thread head = waiters.peek();
            if (head == Thread.currentThread()) {
              // 再次尝试抢锁
              if (!tryLock()) {
                // 若抢锁失败,挂起线程,继续等待
                LockSupport.park();
              } else {
                // 若成功,就出队列
                waiters.poll();
                return;
              }
            } else {
              // 如果不是队列头部,就挂起线程
              LockSupport.park();
            }
          }
        }
      }
    
      public boolean tryUnlock() {
        // 判断,是否是当前线程占有锁,若不是,抛异常
        if (owner.get() != Thread.currentThread()) {
          throw new IllegalMonitorStateException();
        } else {
          // 如果是,就将count-1  若count变为0 ,则解锁成功
          int ct = count.get();
          int nextc = ct - 1;
          count.set(nextc);
          // 判断count值是否为0
          if (nextc == 0) {
            owner.compareAndSet(Thread.currentThread(), null);
            return true;
          } else {
            return false;
          }
        }
      }
    
      @Override
      public void unlock() {
        // 尝试释放锁
        if (tryUnlock()) {
          // 获取队列头部, 如果不为null则将其唤醒
          Thread thread = waiters.peek();
          if (thread != null) {
            LockSupport.unpark(thread);
          }
        }
      }
    
      @Override
      public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
      }
    
      @Override
      public void lockInterruptibly() throws InterruptedException {}
    
      @Override
      public Condition newCondition() {
        return null;
      }
    }
    

    synchronized VS Lock

    synchronized

    优点:

    • 使用简单,语义清晰,哪里需要点哪里
    • 由JVM提供,提供了多种优化方案(锁粗化,锁消除,偏向锁,轻量级锁)
    • 锁的释放由虚拟机完成,不用人工干预,降低了死锁的可能性

    缺点:悲观的排他锁,无法实现锁的高级功能如公平锁,读写锁等

    Lock

    优点:可以实现synchronized无法实现的锁的高级功能如公平锁,读写锁等,同时还可以实现更多的功能 

    缺点:需手动释放锁unlock,使用不当容易造成死锁

     

    结论: 两者都是可重入锁,synchronized可以类比为傻瓜相机,提供了固定的功能,而Lock可以类比为单方,可以根据需要调节所需的功能

     

     

  • 相关阅读:
    用 xampp 在 windows/Linux 下搭建代理服务器
    DOM 元素 属性和方法
    JavaScript入门培训材料(Copy至此以作备份)
    JavaScript 关键字快速匹配
    JS参考书籍
    chrome 阻止跨域操作的解决方法 --disable-web-security
    可输入自动匹配Select——jquery ui autocomplete
    【消息队列】如何保证消息的顺序性
    【消息队列】如何处理消息丢失的问题
    【消息队列】kafka是如何保证消息不被重复消费的
  • 原文地址:https://www.cnblogs.com/coding-diary/p/11247039.html
Copyright © 2020-2023  润新知