本文源码:GitHub·点这里 || GitEE·点这里

一、Lock体系结构

1、基础接口简介

Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接口,基本关系如下:

Lock接口

Java并发编程中资源加锁的根接口之一,规定了资源锁使用的几个基础方法。

ReentrantLock类

实现Lock接口的可重入锁,即线程如果获得当前实例的锁,并进入任务方法,在线程没有释放锁的状态下,可以再次进入任务方法,特点:互斥排它性,即同一个时刻只有一个线程进入任务。

Condition接口

Condition接口描述可能会与锁有关联的条件变量,提供了更强大的功能,例如在线程的等待/通知机制上,Conditon可以实现多路通知和选择性通知。

2、使用案例

生产消费模式

写线程向容器中添加数据,读线程从容器获取数据,如果容器为空时,读线程等待。

public class LockAPI01 {

    private static Lock lock = new ReentrantLock() ;
private static Condition condition1 = lock.newCondition() ;
private static Condition condition2 = lock.newCondition() ; public static void main(String[] args) throws Exception {
List<String> dataList = new ArrayList<>() ;
ReadList readList = new ReadList(dataList);
WriteList writeList = new WriteList(dataList);
new Thread(readList).start();
TimeUnit.SECONDS.sleep(2);
new Thread(writeList).start();
}
// 读数据线程
static class ReadList implements Runnable {
private List<String> dataList ;
public ReadList (List<String> dataList){
this.dataList = dataList ;
}
@Override
public void run() {
lock.lock();
try {
if (dataList.size() != 2){
System.out.println("Read wait...");
condition1.await();
}
System.out.println("ReadList WakeUp...");
for (String element:dataList){
System.out.println("ReadList:"+element);
}
condition2.signalAll();
} catch (InterruptedException e){
e.fillInStackTrace() ;
} finally {
lock.unlock();
}
}
}
// 写数据线程
static class WriteList implements Runnable {
private List<String> dataList ;
public WriteList (List<String> dataList){
this.dataList = dataList ;
}
@Override
public void run() {
lock.lock();
try {
dataList.add("Java") ;
dataList.add("C++") ;
condition1.signalAll();
System.out.println("Write over...");
condition2.await();
System.out.println("Write WakeUp...");
} catch (InterruptedException e){
e.fillInStackTrace() ;
} finally {
lock.unlock();
}
}
}
}

这个生产消费模式和生活中的点餐场景极为类似,用户下单,通知后厨烹饪,烹饪完成之后通知送餐。

顺序执行模式

既然线程执行可以互相通知,那也可以基于该机制实现线程的顺序执行,基本思路:在一个线程执行完毕后,基于条件唤醒下个线程。

public class LockAPI02 {
public static void main(String[] args) {
PrintInfo printInfo = new PrintInfo() ;
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(new PrintA(printInfo));
service.execute(new PrintB(printInfo));
service.execute(new PrintC(printInfo));
}
}
class PrintA implements Runnable {
private PrintInfo printInfo ;
public PrintA (PrintInfo printInfo){
this.printInfo = printInfo ;
}
@Override
public void run() {
printInfo.printA ();
}
}
class PrintB implements Runnable {
private PrintInfo printInfo ;
public PrintB (PrintInfo printInfo){
this.printInfo = printInfo ;
}
@Override
public void run() {
printInfo.printB ();
}
}
class PrintC implements Runnable {
private PrintInfo printInfo ;
public PrintC (PrintInfo printInfo){
this.printInfo = printInfo ;
}
@Override
public void run() {
printInfo.printC ();
}
}
class PrintInfo {
// 控制下个执行的线程
private String info = "A";
private ReentrantLock lock = new ReentrantLock();
// 三个线程,三个控制条件
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void printA (){
try {
lock.lock();
while (!info.equals("A")) {
conditionA.await();
}
System.out.print("A");
info = "B";
conditionB.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB (){
try {
lock.lock();
while (!info.equals("B")) {
conditionB.await();
}
System.out.print("B");
info = "C";
conditionC.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC (){
try {
lock.lock();
while (!info.equals("C")) {
conditionC.await();
}
System.out.print("C");
info = "A";
conditionA.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

该案例经常出现在多线程的面试题中,如何实现ABC的顺序打印问题,基本思路就是基于线程的等待通知机制,但是实现方式很多,上述只是其中一种方式。

二、读写锁机制

1、基础API简介

重入锁的排它特性决定了性能会产生瓶颈,为了提升性能问题,JDK中还有另一套读写锁机制。读写锁中维护一个共享读锁和一个排它写锁,在实际开发中,读的场景还是偏多的,所以读写锁可以很好的提高并发性。

读写锁相关结构中两个基础API:ReadWriteLock接口和ReentrantReadWriteLock实现类,基本关系如下:

ReadWriteLock

提供两个基础方法,readLock获取读机制锁,writeLock获取写机制锁。

ReentrantReadWriteLock

接口ReadWriteLock的具体实现,特点:基于读锁时,其他线程可以进行读操作,基于写锁时,其他线程读、写操作都禁止。

2、使用案例

读写分离模式

通过读写锁机制,分别向数据容器Map中写入数据和读取数据,以此验证读写锁机制。

public class LockAPI03 {
public static void main(String[] args) throws Exception {
DataMap dataMap = new DataMap() ;
Thread read = new Thread(new GetRun(dataMap)) ;
Thread write = new Thread(new PutRun(dataMap)) ;
write.start();
Thread.sleep(2000);
read.start();
}
}
class GetRun implements Runnable {
private DataMap dataMap ;
public GetRun (DataMap dataMap){
this.dataMap = dataMap ;
}
@Override
public void run() {
System.out.println("GetRun:"+dataMap.get("myKey"));
}
}
class PutRun implements Runnable {
private DataMap dataMap ;
public PutRun (DataMap dataMap){
this.dataMap = dataMap ;
}
@Override
public void run() {
dataMap.put("myKey","myValue");
}
}
class DataMap {
Map<String,String> dataMap = new HashMap<>() ;
ReadWriteLock rwLock = new ReentrantReadWriteLock() ;
Lock readLock = rwLock.readLock() ;
Lock writeLock = rwLock.writeLock() ; // 读取数据
public String get (String key){
readLock.lock();
try{
return dataMap.get(key) ;
} finally {
readLock.unlock();
}
}
// 写入数据
public void put (String key,String value){
writeLock.lock();
try{
dataMap.put(key,value) ;
System.out.println("执行写入结束...");
Thread.sleep(10000);
} catch (Exception e) {
System.out.println("Exception...");
} finally {
writeLock.unlock();
}
}
}

说明:当put方法一直在睡眠状态时,因为写锁的排它性质,所以读方法是无法执行的。

三、基础工具类

LockSupport简介

LockSupprot定义一组公共静态方法,这些方法提供最基本的线程阻塞和唤醒功

能。

基础方法

park():当前线程阻塞,当前线程被中断或调用unpark方法,park()方法中返回;

park(Object blocker):功能同park(),传入Object对象,记录导致线程阻塞的阻塞对象,方便问题排查;

parkNanos(long nanos):指定时间nanos内阻塞当前线程,超时返回;

unpark(Thread thread):唤醒指定处于阻塞状态的线程;

代码案例

该流程在购物APP上非常常见,当你准备支付时放弃,会有一个支付失效,在支付失效期内可以随时回来支付,过期后需要重新选取支付商品。

public class LockAPI04 {
public static void main(String[] args) throws Exception {
OrderPay orderPay = new OrderPay("UnPaid") ;
Thread orderThread = new Thread(orderPay) ;
orderThread.start();
Thread.sleep(3000);
orderPay.changeState("Pay");
LockSupport.unpark(orderThread);
}
}
class OrderPay implements Runnable {
// 支付状态
private String orderState ;
public OrderPay (String orderState){
this.orderState = orderState ;
}
public synchronized void changeState (String orderState){
this.orderState = orderState ;
}
@Override
public void run() {
if (orderState.equals("UnPaid")){
System.out.println("订单待支付..."+orderState);
LockSupport.park(orderState);
}
System.out.println("orderState="+orderState);
System.out.println("订单准备发货...");
}
}

这里基于LockSupport中park和unpark控制线程状态,实现的等待通知机制。

四、源代码地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

推荐文章:并发编程系列

序号 文章标题
01 Java并发:线程的创建方式,状态周期管理
02 Java并发:线程核心机制,基础概念扩展
03 Java并发:多线程并发访问,同步控制
04 Java并发:线程间通信,等待/通知机制
05 Java并发:悲观锁和乐观锁机制

Java并发编程(06):Lock机制下API用法详解的更多相关文章

  1. 【多线程】Java并发编程:Lock(转载)

    原文链接:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...

  2. [转载] java并发编程:Lock(线程锁)

    作者:海子 原文链接: http://www.cnblogs.com/dolphin0520/p/3923167.html 出处:http://www.cnblogs.com/dolphin0520/ ...

  3. Java并发编程:Lock(转)

    本文转自:http://www.cnblogs.com/dolphin0520/p/3923167.html Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized ...

  4. 5、Java并发编程:Lock

    Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.l ...

  5. Hadoop生态圈-zookeeper的API用法详解

    Hadoop生态圈-zookeeper的API用法详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.测试前准备 1>.开启集群 [yinzhengjie@s101 ~] ...

  6. 【java并发编程】Lock & Condition 协调同步生产消费

    一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...

  7. Java并发编程之锁机制

    锁分类 悲观锁与乐观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不加锁的并发操作一定会出问题 ...

  8. JavaEE基础(02):Servlet核心API用法详解

    本文源码:GitHub·点这里 || GitEE·点这里 一.核心API简介 1.Servlet执行流程 Servlet是JavaWeb的三大组件之一(Servlet.Filter.Listener) ...

  9. 【转】Java并发编程:Lock

    阅读目录 一.synchronized的缺陷 二.java.util.concurrent.locks包下常用的类 三.锁的相关概念介绍 来自: http://www.importnew.com/18 ...

随机推荐

  1. Java实现 LeetCode 13 罗马数字转整数

    13. 罗马数字转整数 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 ...

  2. 痞子衡嵌入式:降低刷新率是定位LCD花屏显示问题的第一大法(i.MXRT1170, 1280x480 LVDS)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT1170上LCD花屏显示问题的分析解决经验. 痞子衡最近这段时间在参与一个基于i.MXRT1170的大项目(先保个密),需要 ...

  3. 多态的C语言实现版本

    #ifndef _51_2_H #define _51_2_H typedef void Demo; typedef void Derived; Demo* Demo_create(int i,int ...

  4. Elasticsearch系列---生产数据备份恢复方案

    前言 生产环境中运行的组件,只要有数据存储,定时备份.灾难恢复是必修课,mysql数据库的备份方案已经非常成熟,Elasticsearch也同样有成熟的数据备份.恢复方案,我们来了解一下. 概要 本篇 ...

  5. [AGC043-D]Merge Triplets

    题目   点这里看题目. 分析   我们不妨来考虑一下生成的序列有什么性质.   为了方便表示,我们将序列\(S\)的第\(i\)项写为\(S[i]\).   首先考虑如果所有的\(A\)序列都是递增 ...

  6. IOS 发布 程序截图问题

    特别要注意那个有无状态栏时的像素要求 **注意:在截屏模拟器的时候,请把模拟器的Scale设置成100%(Window->Scale->100%) 开模拟器截图,运行每一个iOS型号,然后 ...

  7. [每日一题2020.06.16] leetcode双周赛T3 5423 找两个和为目标值且不重叠的子数组 DP, 前缀和

    题目链接 给你一个整数数组 arr 和一个整数值 target . 请你在 arr 中找 两个互不重叠的子数组 且它们的和都等于 target .可能会有多种方案,请你返回满足要求的两个子数组长度和的 ...

  8. (九)maven-compiler-plugin 插件详解

    <plugin> <!-- 指定maven编译的jdk版本,如果不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 --> <groupId> ...

  9. a标签伪类link,hover,active,visited,focus区别

    <div id="content"> <h3><a class="a1" href="#">a标签伪类l ...

  10. Python实现梯度法(最速上升(下降)法)寻找函数极大(极小)值

    首先简介梯度法的原理.首先一个实值函数$R^{n} \rightarrow R$的梯度方向是函数值上升最快的方向.梯度的反方向显然是函数值下降的最快方向,这就是机器学习里梯度下降法的基本原理.但是运筹 ...