在JDK5里面,提供了一个Lock接口。该接口通过底层框架的形式为设计更面向对象、可更加细粒度控制线程代码、更灵活控制线程通信提供了基础。实现Lock接口且使用得比较多的是可重入锁(ReentrantLock)以及读写锁(ReentrantReadWriteLock)。
1. ReentrantLock
在Java多线程(二) 多线程的锁机制 里面,已经总结过通过使用Synchronized关键字实现线程内的方法锁定。但使用Synchronized关键字有一些局限性,上锁和释放锁是由JVM决定的,用户没法上锁和释放进行控制。那么问题就来了:假如有一个线程业务类管理某一全局变量的读和写。对于每条线程,在读的时候数据是共享的可以让多个线程同时去读。但有某个线程在对该全局变量进行写的时候,其他的线程都不能够对变量进行读或者写(对应数据库内的读共享写互斥)。可能会有如下伪代码:
1 package com.scl.thread.lock;
2
3 public class MyCounter
4 {
5 public int count;
6
7 public int readCount()
8 {
9 return this.count;
10 }
11
12 public void writeCount()
13 {
14 synchronized(this)
15 {
16 count++;
17 }
18 }
19 }
尽管对写操作进行了空值,但是在写的时候,线程还是能够进行读操作!由此,JDK5并发库内提供了Lock接口。程序员可以通过实现Lock接口对代码块进行更灵活的锁控制.
JDK5通过使用AbstractQueuedSynchronizer(简写为AQS)抽象类把Lock接口的功能实现了一大部分功能,如果程序员需要编写一套跟有自身逻辑的"锁"时,可以简单地通过实现public boolean tryAcquire(int acquire) 及 public boolean tryRelease(int releases) 进行加锁及释放锁功能。AQS为整个并发内容的核心框架,类似synchronized的锁(ReentrantLock :可重入锁)就是使用了AQS框架进行构建。ReentrantLock提供了一个可中断、拥有并发竞争机制[指线程对锁的竞争方式:公平竞争或不公平竞争]的方式,该部分的内容的源码分析可以查看: ReentrantLock 实现原理深入探究
正如ReentrantLock跟Synchronized关键字所使用的功能基本一样,而且Synchronized还能自己释放锁,那什么时候使用ReentrantLock?
① 在中断线程的时候,可以使用ReentrantLock进行控制
如线程1有一个耗时很大的任务在执行,执行时线程2必须进行等待。当线程1执行的任务时间实在太长了,线程2放弃等待进行线程后续的操作。该情况下如果使用Synchronized,只能通过抛出异常的形式进行异常操作。
② 多条件变量通讯
如有3条线程,线程1完成任务后通知线程2执行,线程2执行完业务逻辑以后通知线程3执行,线程3执行完通知线程1继续执行。用Synchronized关键字很难处理这种问题。用Lock却可以很好的处理这些内容。当然,线程1 、2、3 同样地可以换由一个线程组去执行这些任务。
1.1 可中断的线程控制
1.1.1 Java的线程中断机制
Java中断线程可以通过实例方法: stop 或 interrupt 进行线程中断,两者有什么区别?先查看以下两段代码及运行结果。
1 package com.scl.thread.interrupt; 2 3 public class TestInterrupt 4 { 5 // 各线程可见的线程状态标志位 6 public static volatile boolean isStop = false; 7 8 public static void main(String[] args) throws InterruptedException 9 { 10 // 创建三条线程,线程1使用stop方法中断,线程2使用interrupt方法中断,线程3与线程2比较使用了interrupt后是否因中断退出 11 Thread th1 = new Thread(new SubThread1(), "SubThread1"); 12 Thread th2 = new Thread(new SubThread2(), "SubThread2"); 13 Thread th3 = new Thread(new SubThread3(), "SubThread3"); 14 15 System.out.println("==============subThread1 code block result=============="); 16 System.out.println("Main Thread call subThread1 to start"); 17 th1.start(); 18 Thread.sleep(3000); 19 System.out.println("Main Thread start to stop subThread1"); 20 th1.stop(); 21 System.out.println("subThread1 was stopped by Main Thread"); 22 // 等待子线程进行stop,让子线程有充分时间处理相关业务 23 Thread.sleep(20); 24 System.out.println("==================================================="); 25 Thread.sleep(20); 26 27 System.out.println("==============subThread2 code block result=============="); 28 System.out.println("Main Thread call subThread2 to start"); 29 th2.start(); 30 Thread.sleep(3000); 31 System.out.println("Main Thread start to interrupt subThread2"); 32 // 设置标志位,令子线程2可以按顺序退出 33 isStop = true; 34 th2.interrupt(); 35 // 等待子线程进行interrupt,让子线程有充分时间处理相关业务 36 Thread.sleep(20); 37 System.out.println(" subThread2 was interruptted by Main Thread"); 38 System.out.println("==================================================="); 39 Thread.sleep(20); 40 41 System.out.println("==============subThread3 code block result=============="); 42 System.out.println("Main Thread call subThread3 to start"); 43 th3.start(); 44 Thread.sleep(3000); 45 System.out.println("Main Thread start to interrupt subThread3"); 46 th2.interrupt(); 47 // 等待子线程进行interrupt,让子线程有充分时间处理相关业务 48 Thread.sleep(20); 49 System.out.println("subThread3 was interrupted by Main Thread"); 50 System.out.println("==================================================="); 51 Thread.sleep(20); 52 53 System.out.println("Main Thread end"); 54 } 55 } 56 57 class SubThread1 implements Runnable 58 { 59 @Override 60 public void run() 61 { 62 while (!TestInterrupt.isStop) 63 { 64 try 65 { 66 // 子线程1进行睡眠 67 Thread.sleep(2000); 68 } 69 catch (InterruptedException e) 70 { 71 e.printStackTrace(); 72 } 73 74 System.out.println(Thread.currentThread().getName() + " is running..."); 75 } 76 // 调用stop方法,该语句不会被执行,因为线程整个退出了 77 System.out.println(Thread.currentThread().getName() + " is ready to cancle"); 78 } 79 } 80 81 class SubThread2 implements Runnable 82 { 83 @Override 84 public void run() 85 { 86 while (!TestInterrupt.isStop) 87 { 88 try 89 { 90 // 子线程1进行睡眠 91 Thread.sleep(200); 92 } 93 catch (InterruptedException e) 94 { 95 e.printStackTrace(); 96 } 97 98 System.out.println(Thread.currentThread().getName() + " is running..."); 99 } 100 // 使用interrupt方法,在发现线程2被阻塞或休眠(sleep)的情况下,会收到一个interrupt的异常。但不会终止线程,仅设置线程是否可以中断的标志位 101 System.out.println(Thread.currentThread().getName() + " is ready to cancle"); 102 } 103 } 104 105 // 调用interrupt方法,对比子线程2,发现使用interrupt方法根本没有中断整个线程,设置后线程也没有进行退出。一直运行 106 class SubThread3 implements Runnable 107 { 108 @Override 109 public void run() 110 { 111 // 使用true代替标志位,判断调用interrupt方法后是否正常中断线程 112 while (true) 113 { 114 try 115 { 116 Thread.sleep(2000); 117 } 118 catch (InterruptedException e) 119 { 120 e.printStackTrace(); 121 } 122 System.out.println(Thread.currentThread().getName() + " is running..."); 123 } 124 } 125 }