本文源码: GitHub. 点这里 https://github.com/cicadasmile/java-base-parent || GitEE. 点这里 https://gitee.com/cicadasmile/java-base-parent
一, 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
来源: http://www.tuicool.com/articles/FzeM7nu