转载

Java并发编程:Lock

locks相关类

锁相关的类都在包java.util.concurrent.locks下,有以下类和接口:

|---AbstractOwnableSynchronizer
|---AbstractQueuedLongSynchronizer
|---AbstractQueuedSynchronizer
|---Condition
|---Lock
|---LockSupport
|---ReadWriteLock
|---ReentrantLock
|---ReentrantReadWriteLock

接口摘要:

接口 摘要
Condition Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
Lock Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
ReadWriteLock ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。

类摘要:

摘要
AbstractOwnableSynchronizer 可以由线程以独占方式拥有的同步器。
AbstractQueuedLongSynchronizer 以 long 形式维护同步状态的一个 AbstractQueuedSynchronizer 版本。
AbstractQueuedSynchronizer 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。
LockSupport 用来创建锁和其他同步类的基本线程阻塞原语。
ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
ReentrantReadWriteLock 支持与 ReentrantLock 类似语义的 ReadWriteLock 实现。
ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.readLock() 方法返回的锁。
ReentrantReadWriteLock.WriteLock ReentrantReadWriteLock.writeLock() 方法返回的锁。

synchronized与lock

synchronized对比lock:
1、synchronized是Java语言的关键字属于内置特性,Lock是一个类
2、使用synchronized不需要用户去手动释放锁,使用Lock需要在finally手动释放锁,不然容易造成线程死锁

详细对比见下面的表格:

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步

常用类

Lock

Lock是一个接口:

public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();
}

下面来逐个讲述Lock接口中每个方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。

lock()

  lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
  由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;
lock.lock();
try{//处理任务
}catch(Exception ex){}finally{lock.unlock();   //释放锁
}

tryLock()、tryLock(long time, TimeUnit unit)

  tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

  tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

  所以,一般情况下通过tryLock来获取锁时是这样使用的:

Lock lock = ...;
if(lock.tryLock()) {try{//处理任务}catch(Exception ex){}finally{lock.unlock();   //释放锁}
}else {//如果不能获取锁,则直接做其他事情
}

lockInterruptibly()

  lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就是说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

  因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException {lock.lockInterruptibly();try {  }finally {lock.unlock();}
}

  注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
  因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

锁类型

Java中存在以下几种锁:

  • 可重入锁:在执行对象中所有同步方法不用再次获得锁(可看一个使用示例)

  • 可中断锁:在等待获取锁过程中可中断

  • 公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利

  • 读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写

可重入锁ReentrantLock

ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。

例子1:lock()的使用
可以类似于Synchronized的用法,定义一个类,新建一个该类的对象用于线程间同步,在类里面定义锁的对象。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockTest {public static void main(String[] args) {new LockTest().init();}private void init(){final Outputer outputer = new Outputer();new Thread(new Runnable(){public void run() {while(true){try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}outputer.output("zhangxiaoxiang");}}}).start();new Thread(new Runnable(){public void run() {while(true){try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}outputer.output("lihuoming");}}}).start();}static class Outputer{Lock lock = new ReentrantLock();public void output(String name){lock.lock();try{System.out.println(name);}finally{lock.unlock();}}}
}

输出:

zhangxiaoxiang
lihuoming
lihuoming
zhangxiaoxiang
zhangxiaoxiang
lihuoming
...

注意:输出的字符串顺序不定,个数也不定。

例子2:tryLock()的使用
这里相比例子1只修改了Outputer类,main方法一样。

static class Outputer{Lock lock = new ReentrantLock();public void output(String name){if (lock.tryLock()) {try{System.out.println(name + "得到锁");}finally{lock.unlock();System.out.println(name + "释放锁");}} else {System.out.println(name + "获取锁失败");}}
}

输出:

lihuoming得到锁
zhangxiaoxiang获取锁失败
lihuoming释放锁
zhangxiaoxiang得到锁
zhangxiaoxiang释放锁
lihuoming得到锁
lihuoming释放锁
...

注意:输出的字符串顺序不定,个数也不定。

例子3:lockInterruptibly()的使用
执行lockInterruptibly()方法的方法中,需要将异常InterruptedException抛出,在等待锁的线程可调用interrupt()方法中断,即可触发异常InterruptedException,然后可以在catch中执行相应的操作。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class LockTest {public static void main(String[] args) {new LockTest().init();}private void init(){final Outputer outputer = new Outputer();Thread thread1 = new Thread(new Runnable(){public void run() {String name = "zhangxiaoxiang";try {Thread.sleep(10);outputer.output(name);} catch (InterruptedException e) {System.out.println(name + "被中断");}}});thread1.start();Thread thread2 = new Thread(new Runnable(){public void run() {String name = "lihuoming";try {Thread.sleep(10);outputer.output(name);} catch (InterruptedException e) {System.out.println(name + "被中断");}}});thread2.start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}thread2.interrupt();}static class Outputer{Lock lock = new ReentrantLock();//将InterruptedException抛出public void output(String name) throws InterruptedException {System.out.println(name + "试图执行output方法");lock.lockInterruptibly();try{System.out.println(name + "得到锁");long startTime = System.currentTimeMillis();for(    ;     ;) {if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)break;}}finally{System.out.println(name + "执行了finally");lock.unlock();System.out.println(name + "释放锁");}}}
}

输出:

zhangxiaoxiang试图执行output方法
zhangxiaoxiang得到锁
lihuoming试图执行output方法
lihuoming被中断

运行之后,发现thread2能够被正确中断

在jdk源码中的一个运用就是类ArrayBlockingQueue的方法。该方法中有以下几点注意:
1、使用lock.lockInterruptibly()需抛出异常InterruptedException
2、使用了Condition
3、在finally中关闭锁

 /*** Inserts the specified element at the tail of this queue, waiting* up to the specified wait time for space to become available if* the queue is full.** @throws InterruptedException {@inheritDoc}* @throws NullPointerException {@inheritDoc}*/public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {checkNotNull(e);long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length) {if (nanos <= 0)return false;/** notFull是一个Condition对象,** Condition for waiting puts ** private final Condition notFull;*/nanos = notFull.awaitNanos(nanos);}insert(e);return true;} finally {lock.unlock();}}

读写锁ReadWriteLock

ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock {/*** Returns the lock used for reading.** @return the lock used for reading.*/Lock readLock();/*** Returns the lock used for writing.** @return the lock used for writing.*/Lock writeLock();
}

一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。ReentrantReadWriteLock实现了ReadWriteLock接口。

ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。

读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,由JVM控制。

注意:此锁最多支持 65535 个递归写入锁和 65535 个读取锁。试图超出这些限制将导致锁方法抛出 Error。

下面给出构造函数和常用方法的简要说明:

类ReentrantReadWriteLock

  • ReentrantReadWriteLock(boolean fair): 使用给定的公平策略创建一个新的 ReentrantReadWriteLock。
  • ReentrantReadWriteLock():使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock

类ReentrantReadWriteLock的方法

返回类型 方法
ReentrantReadWriteLock.ReadLock readLock() 返回用于读取操作的锁
ReentrantReadWriteLock.WriteLock writeLock() 返回用于写入操作的锁

下面给出示例代码:

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockTest {public static void main(String[] args) {final Queue3 q3 = new Queue3();for(int i=0;i<3;i++){final Thread readThread = new Thread() {public void run() {while (true) {q3.get();}}};readThread.setName("read-"+i);readThread.start();final Thread writeThread = new Thread(){public void run(){while(true){q3.put(new Random().nextInt(10000));}}           };writeThread.setName("write-"+i);writeThread.start();}}
}class Queue3{private Object data = null;ReadWriteLock rwl = new ReentrantReadWriteLock();public void get(){rwl.readLock().lock();try {System.out.println(Thread.currentThread().getName() + " be ready to read data!");Thread.sleep((long)(Math.random()*1000));System.out.println(Thread.currentThread().getName() + " have read data :" + data);} catch (InterruptedException e) {e.printStackTrace();}finally{rwl.readLock().unlock();}}public void put(Object data){rwl.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + " be ready to write data!");                  Thread.sleep((long)(Math.random()*1000));this.data = data;       System.out.println(Thread.currentThread().getName() + " have write data: " + data);                 } catch (InterruptedException e) {e.printStackTrace();}finally{rwl.writeLock().unlock();}}
}

输出:

read-0 be ready to read data!
read-1 be ready to read data!
read-0 have read data :null
read-1 have read data :null
write-1 be ready to write data!
write-1 have write data: 3713
write-1 be ready to write data!
write-1 have write data: 3420
...

对输出结果进行分析:be ready to read data!have read data并不是先后出现的,中间可以夹着be ready to read data!说明读锁之间不互斥。

面试题:
缓存系统:取数据,需调用public Object getData(String key)方法,先检查缓存有没有这个数据,如果有就直接返回,如果没有,就从数据库中查找这个数,然后写入缓存。
如果使用synchronized对getData加锁,那么getData方法只能被一个读线程执行,其他读操作就得等待,这里可以使用一个读写锁,只有在写的时候才需要互斥


import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class CacheDemo {private Map<String, Object> cache = new HashMap<String, Object>();public static void main(String[] args) {// TODO Auto-generated method stub}private ReadWriteLock rwl = new ReentrantReadWriteLock();public  Object getData(String key){rwl.readLock().lock();Object value = null;try{value = cache.get(key);if(value == null){rwl.readLock().unlock();rwl.writeLock().lock();try{//再次进行判断,防止多个写线程堵在这个地方重复写if(value==null){value = "aaaa"; //设置新值}}finally{rwl.writeLock().unlock();}//设置完成 释放写锁,恢复读写状态rwl.readLock().lock();}}finally{rwl.readLock().unlock();}return value;}
}

其他更多有关ReentrantReadWriteLock后面补充。

Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,,阻塞队列实际上是使用了Condition来模拟线程间协作。

synchronized常与wait、notify等方法使用,Condition常与await、signal等方法使用。

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
  • 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()。

Condition中的long awaitNanos(long nanosTimeout) throws InterruptedException方法传入一个等待的微秒时间,该方法返回了所剩毫微秒数的一个估计值,以等待所提供的 nanosTimeout 值的时间,如果超时,则返回一个小于等于 0 的值。可以用此值来确定在等待返回但某一等待条件仍不具备的情况下,是否要再次等待,以及再次等待的时间。此方法的典型用法采用以下形式(上面讲ArrayBlockingQueue的public E poll(long timeout, TimeUnit unit)方法中就用到这个方法):

 synchronized boolean aMethod(long timeout, TimeUnit unit) {long nanosTimeout = unit.toNanos(timeout);while (!conditionBeingWaitedFor) {if (nanosTimeout > 0)nanosTimeout = theCondition.awaitNanos(nanosTimeout);elsereturn false;}// ... }

代码示例:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ConditionCommunication {public static void main(String[] args) {final Business business = new Business();new Thread(new Runnable() {public void run() {for (int i = 1; i <= 50; i++) {business.sub(i);}}}).start();for (int i = 1; i <= 50; i++) {business.main(i);}}static class Business {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();private boolean bShouldSub = true;public void sub(int i) {lock.lock();try {while (!bShouldSub) {try {condition.await();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 10; j++) {System.out.println("sub thread sequence of " + j + ",loop of " + i);}bShouldSub = false;condition.signal();} finally {lock.unlock();}}public void main(int i) {lock.lock();try {while (bShouldSub) {try {condition.await();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}for (int j = 1; j <= 100; j++) {System.out.println("main thread sequence of " + j + ",loop of " + i);}bShouldSub = true;condition.signal();} finally {lock.unlock();}}}
}

同样:await()方法需要放在while循环中。
更多参考:
对比synchronized+notify的使用可以参考:04_张孝祥Java多线程传统线程同步通信技术
与传统的同步对比可参考:线程间协作的两种方式:wait、notify、notifyAll和Condition

参考

详解synchronized与Lock的区别与使用
张孝祥_Java多线程与并发库高级应用04
jdk api
线程间协作的两种方式:wait、notify、notifyAll和Condition

11_张孝祥_多线程_线程锁技术相关推荐

  1. java 多线程 张孝祥_多线程11_张孝祥 java5的线程锁技术

    本例子因为两个线程公用同线程中,使用同一个对象,实现了他们公用一把锁,实现了同一个方法的互斥. package locks; /** *会被打乱的效果 */ public class LockTest ...

  2. Java高并发编程:线程锁技术

    目录 1 什么是线程锁 2 synchronized 1. 对象锁 2. 修饰对象方法 3. 类锁 4. 对象锁和类锁 5. 卖火车票示例 6. 生产一个消费一个示例 3 Lock 3.1 重入锁 R ...

  3. JAVA入门_多线程_邮局派发信件

    JAVA入门_多线程_邮局派发信件 Postman package cn.campsg.java.experiment.entity;public class Postman {private Str ...

  4. php和python的多线程,Python多线程以及线程锁简单理解(代码)

    本篇文章给大家带来的内容是关于Python多线程以及线程锁简单理解(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 多线程threading 模块创建线程创建自己的线程类线程通 ...

  5. python-Threading多线程之线程锁

    Threading多线程之线程锁 这里先说一下加锁的机制,其是如何实现线程保护的.这个实现的大致过程为:首先在需要同步的代码块前面加上lock.acquire()语句,表示需要先成功获取该锁,才能继续 ...

  6. Java多线程(四)线程锁

    6.锁 由于多个线程是共同占有所属进程的资源和地址空间的,那么就会存在一个问题: 如果多个线程要同时访问某个资源,怎么处理? 在Java并发编程中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开 ...

  7. 多线程中线程锁的使用

    在多线程的程序编写中,常常遇到共享资源使用冲突解决的苦恼.终于看到并测试了一种简单方法. 线程锁的5个要素: CRITICAL_SECTION g_cs;  //定义线程锁 InitializeCri ...

  8. python多线程之线程锁(Lock)和递归锁(RLock)实例

    一.线程锁 Threading模块为我们提供了一个类,Threading.Lock锁.我们创建一个该类对象,在线程函数执行前,"抢占"该锁,执行完成后,"释放" ...

  9. java 线程开销_多线程的线程开销

    多线程中两个必要的开销:线程的创建.上下文切换 创建线程: 创建线程使用是直接向系统申请资源的,对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存.列入调度,同时在线程切换的时候还要执 ...

最新文章

  1. 智慧旅游建设方案_智慧灯杆及智慧交通设施建设方案
  2. php如何导入大文件数据库,PHP读取CSV大文件导入数据库的示例
  3. python中怎么取两个列表 集合的交集
  4. boost::graph模块实现深度优先搜索 和广度优先搜索算法的测试程序
  5. SUSE Linux启动过程执行脚本顺序
  6. centos常用命令_二、Docker镜像是什么?Docker常用命令
  7. C语言俄罗斯方块代码
  8. flex属性-flex:1到底是什么
  9. 大哥都是从小弟做起的
  10. 血液透析机最全学习手册
  11. Android流式布局FlowLayout,一款针对Tag的布局
  12. EtherCAT运动控制器在数控加工手轮随动中的应用之C++
  13. 游戏公司如何应对游戏黑产 ?
  14. 春天来了,该播种了。久久荒芜的博客重新耕种起来
  15. 什么叫工业4.0?这篇接地气的文章终于讲懂了!
  16. Java安装与环境配置(Java SE 14)
  17. 人力资源总监HR十项具体工作
  18. idea运行项目出现 Module was compiled with an incompatible version of Kotlin的解决方案
  19. 西湖大学自然语言处理(一)—— 课程简介及大纲
  20. Postman密码错误找回时没有任何反应

热门文章

  1. 离职原因可以说,但要注意三个原则
  2. 检测到弱密码套件:不支持完全前向保密
  3. 基于查表法的永磁同步电机MTPA-MTPV控制仿真模型,查表法,最大电流弱磁控制
  4. unity2d粒子特效
  5. Jackson反序列化List
  6. 跳过Windows安装程序
  7. Python爬虫-爬取福利图片踩坑
  8. OCR图片文字识别,人工手动图片标注软件安装过程
  9. RocketMQ消费优化
  10. 微拍堂通过创新性的文玩体验,让世界感受中华文化的魅力