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在功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等。

<Java><Multi-thread><Lock interface>的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. GreenDao使用解析

    GreenDao是一个轻量级的数据库框架,相比Xutils 等他的速度和效率会更快更好 这里展示GreenDao 的使用方法 ①建立 compile 'org.greenrobot:greendao: ...

  2. js滚动条如何缓慢的回到顶部?

    function top() { let currentPosition, timer timer = setInterval(function () { currentPosition = docu ...

  3. 通俗大白话来理解TCP协议的三次握手和四次断开

    from : https://blog.csdn.net/Neo233/article/details/72866230?locationNum=15&fps=1%20HTTP%E6%8F%A ...

  4. 『TensorFlow』第九弹_图像预处理_不爱红妆爱武装

    部分代码单独测试: 这里实践了图像大小调整的代码,值得注意的是格式问题: 输入输出图像时一定要使用uint8编码, 但是数据处理过程中TF会自动把编码方式调整为float32,所以输入时没问题,输出时 ...

  5. springboot项目线程使用

    下面是一个demo: public class TestThread { private static int nThreads =Runtime.getRuntime().availableProc ...

  6. svn: 提交终止

    今天我遇到了svn 的问题  svn: 提交终止: “/var/www/modelfinance/modules/incomereport/views/purchase” 处于冲突状态 冲突状态搞的我 ...

  7. git忽略未被跟踪和已被跟踪的文件

    git的文件操作本质上来讲是基于文件索引来做追踪的.   至于忽略未跟踪(untrack)文件文件,git提供了三种方式 1 .gitignore 2 git config --global core ...

  8. 一、Redis的数据类型

    一Redis的数据类型 string:字符串 hash:哈希 list:列表 set:集合 zset:有序集合(sorted set) 1.string(字符串) redis最基本的类型.可以理解成与 ...

  9. 使用Spring-data-jpa(1)(三十)

    在实际开发过程中,对数据库的操作无非就“增删改查”.就最为普遍的单表操作而言,除了表和字段不同外,语句都是类似的,开发人员需要写大量类似而枯燥的语句来完成业务逻辑. 为了解决这些大量枯燥的数据操作语句 ...

  10. PHP配置方法

    由于php是一个zip文件(非install版),安装较为简单,解压就行.把解压的 php5.2.1-Win32重命名为 php5.并复制到C盘目录下.即安装路径为 c:\php5 1 找到php目录 ...