• <Java><Multi-thread><Lock interface>


    Overview

    • 介绍java的lock interface.

    Motivation

    • java拥有像synchronized这样的内置锁,那为什么还需要lock这样的外置锁呢?
    • 首先,性能不是选择synchronized或lock的原因,因为jdk6中synchronized的性能已经和lock相差不大。
    • 一般,选择lock是基于lock拥有的以下几个优点(内置锁不具备):
      • 当获取锁时可以有一个等待时间,不会无期限等待下去;
      • 当获取不到锁时,能够响应中断;[lockInterruptibly()]
      • 可以在多读少写的应用场景中,提高性能;  [new Condition()]
      • 可以在获取不到锁时,立即返回false,获取到锁时返回true。[tryLock()]

    Lock Interface

    • public interface Lock {
        void lock();   // acquires the lock, 如果锁被其他线程获取,则进行等待
        
        boolean tryLock();  // 尝试获取锁,成功则返回true,否则返回false
        
        void lockInterruptibly() throws InterruptedException;  // 通过该方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
        
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        
        void unlock();
        
        Condition newCondition();
      }
    • 其中,
      • lockInterruptibly()表面加锁时,当前拥有锁的线程可以被中断;
      • tryLock()则用于尝试获取锁,能获取则返回true,否则返回false;
      • tryLock(long time, TimeUnit unit)与tryLock类似,只是会尝试一段时间;
      • unlock()用于拥有锁的线程释放锁。
      • newCondition()返回一个新的与当前实例绑定的Condition。

    Continue that basic example

    • 对于我们在上面讲的那个经典的生产者消费者的模型。我们已经知道为了防止deadlock,我们用notifyAll()取代了notify()。也就是每次会唤醒所有的存取线程,因为synchronized这种内置锁是加在类实例之上的,put()和take()共用一把锁。
    • 很明显,为了优化这种方式,我们想每次唤醒的是状态能够发生变化的线程。比如说我put()之后就只想唤醒消费者线程,因为此时唤醒生产者线程是无意义的。
    • 这种想法很明显只有通过将两种进程放到不同的等待队列,才能实现。
    • 这就要用到上述Lock接口的newCondition()方法。
    • 基于Lock interface的生产者消费者模型部分代码如下:
    • private final Lock lock = new ReentrantLock();
      
      private final Condition notFullCondition = lock.newCondition();
      private final Condition notEmptyCondition = lock.newCondition();
      
      public void put(Object obj) throws InterruptedException{
        lock.lock();
        try {
          while (count == DEFAULT_BUFFER_SIZE) {
            System.out.println("the buffer is full,wait for a moment for putting ["+obj+"] to the buffer"+",thread:"+Thread.currentThread().getId());
            notFullCondition.await();   // wait for the notFullCondition
          }
          if (tail >= DEFAULT_BUFFER_SIZE)  tail = 0;
          
          buffer[tail] = obj;
          count++;
          System.out.println("success put the data ["+obj+"] to buffer,thread:"+Thread.currentThread().getId());
          
          // then we invoke the thread in the notEmptyCondition wait queue
          notEmptyCondition.signal();
        } finally{
          lock.unlock();
        }
      }
      
      public Object take() throws InterruptedException {
        lock.lock();
        Object res;
        try {
          while (count == EMPTY) {
            System.out.println("the buffer is empty,just wait a moment,thread:"+Thread.currentThread().getId());
            notEmptyCondition.await();   // wait for notEmptyCondition
          }
          res = buffer[header];
          if (++header >= DEFAULT_BUFFER_SIZE) header = 0;
          
          count--;
          if (count < EMPTY)  count = 0;
          
          notFullCondition.signal();  // invoke the threads in the notFullCondition wait queue
        } finally {
          lock.unlock();
        }
        return res;
      }
    • 上述代码中要注意:Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。所以一般来说,使用Lock必须在try catch块中进行,并且在finally块中释放锁,以保证锁一定被释放,防止deadlock的发生。
    • ReentrantLock表示可重入锁,ReentrantLock是唯一实现了的Lock接口类。

    ReentrantLock VS synchronized

    Synchronized

    • 把代码声明为synchronized,可以同时拥有原子性和可见性:
      • 原子性:在同一时刻只有一个线程能对该段代码进行操作,从而防止在多个线程更新共享状态时互相冲突;
      • 可见性:可见性需要对付内存缓存和编译器优化的各种反常行为(在多核处理器中,若多个线程对一个变量进行操作,这多个线程有可能被分配到多个处理器中运行,那么编译器会对代码进行优化:将变量复制一份到自己的片上存储器中,操作完后才赋值回主存,以此来提高速度。同样的,在单核处理器上也存在着由于“备份”造成的问题。)。一般来说,线程以某种不必让其他线程立即可以看到的方式。

    ReentrantLock类

    • 首先,ReentrantLock是一个类,implement Lock接口。通过使用类,而不是作为语言的特性来实现,这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
    • ReentrantLock在功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等。
    满地都是六便士,她却抬头看见了月亮。
  • 相关阅读:
    水平触发与边缘触发
    Leetcode935 骑士拨号器
    leetcodeF47 礼物的最大价值
    leetcodeF42 连续子数组的最大和
    leetcode12 矩阵中的路径 回溯算法
    leetcode14-II 剪绳子II DP 解法配合快速乘取模
    leetcode17.16 按摩师DP
    leetcode530 二叉树的最小绝对差
    PCB genesis大孔加小孔(即卸力孔)实现方法
    PCB genesis短槽加引导孔实现方法
  • 原文地址:https://www.cnblogs.com/wttttt/p/6978178.html
Copyright © 2020-2023  润新知