Java并发编程(06):Lock机制下API用法详解
本文源码: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
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
深入理解EnableAutoConfiguration原理
源码分析@EnableAutoConfiguration在SpringBoot中的加载和实例化过程 万里长征第一步,我们先理解下什么是EnableAutoConfiguration? 什么是EnableAutoConfiguration注解? 在哪? org.springframework.boot.autoconfigure.EnableAutoConfiguration EnableAutoConfiguration概述 我们先来看下源码 package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang...
- 下一篇
【直播预告】计算中间件Linkis开源技术的应用和实践
摘要:一站式开源大数据平台套件WeDataSphere第2期线上 Meetup 7月9日 第2期WeDataSphere线上 Meetup将与大家见面,Linkis项目负责人暨微众银行大数据平台负责人,以及三位社区committer天翼云大数据平台负责人、艾佳生活架构师、MobTech大数据平台专家将做客直播间,同大家分享计算中间件Linkis 在各家公司的精彩应用案例与生产实践,以及Linkis 1.0.0最新版本的Roadmap和架构设计思路,并带来WeDataSphere大数据平台套件的最新开源进展和相关规划。 欢迎大家一起参与到WeDataSphere开源项目中来,我们欢迎在github、gitee以及社群里任何形式的互动与贡献,期待你的加入! GitHub:https://github.com/WeBankFinTech/Linkis Gitee:https://gitee.com/WeBank/Linkis 欢迎扫码入群交流,直播地址也将在开播前发送到WeDataSphere社区用户群,敬请关注。 议程 19:00-19:25 Linkis1.0.0架构设计思路和WeDa...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Hadoop3单机部署,实现最简伪集群
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库