Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock
在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 }
1 ==============subThread1 code block result============== 2 Main Thread call subThread1 to start 3 SubThread1 is running... 4 Main Thread start to stop subThread1 5 subThread1 was stopped by Main Thread 6 =================================================== 7 ==============subThread2 code block result============== 8 Main Thread call subThread2 to start 9 SubThread2 is running... 10 SubThread2 is running... 11 SubThread2 is running... 12 SubThread2 is running... 13 SubThread2 is running... 14 SubThread2 is running... 15 SubThread2 is running... 16 SubThread2 is running... 17 SubThread2 is running... 18 SubThread2 is running... 19 SubThread2 is running... 20 SubThread2 is running... 21 SubThread2 is running... 22 SubThread2 is running... 23 Main Thread start to interrupt subThread2 24 SubThread2 is running... 25 SubThread2 is ready to cancle 26 java.lang.InterruptedException: sleep interrupted 27 at java.lang.Thread.sleep(Native Method) 28 at com.scl.thread.interrupt.SubThread2.run(TestInterrupt.java:91) 29 at java.lang.Thread.run(Thread.java:744) 30 subThread2 was interruptted by Main Thread 31 =================================================== 32 ==============subThread3 code block result============== 33 Main Thread call subThread3 to start 34 SubThread3 is running... 35 Main Thread start to interrupt subThread3 36 subThread3 was interrupted by Main Thread 37 =================================================== 38 Main Thread end 39 SubThread3 is running... 40 SubThread3 is running...
从代码运行结果上面,可以看到两个函数的差异:stop把线程给结束了,而interrupt方法没有结束线程,因此两者总结如下:
① 在线程实例调用stop 方法后把线程给终结了,在子线程1内while以外的代码块将不会被执行
② 在线程实例调用interrupt 方法后,线程没有被终结且该方法通过线程间协作的关系,可以有序地退出线程。且实例线程在被中断后,若线程被阻塞或者休眠的情况下,将收到一个InterruptedException。
③ 子线程3的运行结果充分证实了线程实例调用interrupt方法根本没有把线程给中断,只是把线程的标志状态进行更改。让线程实例在适当的时机进行退出。
由此可见public void interrupt( )并非马上对线程进行中断(强行结束线程),而是通过协作的方法把线程的状态设置为可中断。告知阻塞中的线程在特定的时刻可以对进行终结。同时,Java提供了两个检验线程中断方法① 实例方法 public boolean isInterrupted() ②静态方法 public static boolean interrupted();
两个方法的区别是:静态方法会去清理线程状态信息。实例方法不会清理状态标志。即当线程实例调用interrupt 的前提下,若再调用静态方法,第一次会返回true,后面全返回false;若在前提下,调用实例方法isInterrupt,如果线程正常退出,会一直返回false [判断线程Thread对象是否已经是终止状态,与线程状态无关]。静态方法偏向于判断线程状态。而实例方法更关心线程是否存活。
需要特别注意的是当线程调用interrupt方法时,假如线程在等待锁或者被休眠了。中断状态会被设置为false。JDK的API里面明确指出了这一点。
下面开始查看Lock与synchronized关键字与ReentrantLock的一些区别。
1.1.2 ReentrantLock对线程中断的控制
首先,单纯地使用synchronized关键字不能进行锁中断控制. 在synchronized关键字控制的代码块内,不会因为线程中断而做出相关处理。
先查看使用synchronized关键字在处理线程中断时的结果。
业务逻辑主要为:开辟两条线程,一条线程对文件进行读操作,另一条线程对文件进行写操作。写操作内容需要时间较长,且先执行。读操作后执行,若读线程等待超过4秒。让读线程中断,进行格式化文件。
① 使用接口,区分使用synchronized关键字及Lock方式控制线程中断的业务逻辑
1 package com.scl.thread.interrupt; 2 3 //文件读写接口,使用Synchronized关键字控制线程中断以及使用Lock控制线程中断都实现该接口 4 public interface IFileHandler 5 { 6 boolean isGetReadLock = false; 7 8 void read(); 9 10 void write(); 11 12 void formatFile(); 13 }
② 在synchronized关键字控制代码块的前提下,对线程进行中断的业务逻辑代码。
1 package com.scl.thread.interrupt; 2 3 public class SyncFileHandler implements IFileHandler 4 { 5 private volatile boolean isGetReadLock = false; 6 7 public boolean isGetReadLock() 8 { 9 return isGetReadLock; 10 } 11 12 public void read() 13 { 14 synchronized (FileHandlerByThreads.class.getClass()) 15 { 16 System.out.println(Thread.currentThread().getName() + " start"); 17 // 能进来则设置变量标志位 18 isGetReadLock = true; 19 } 20 } 21 22 // 模拟运行时间比较久的写操作 23 public void write() 24 { 25 try 26 { 27 synchronized (FileHandlerByThreads.class.getClass()) 28 { 29 System.out.println(Thread.currentThread().getName() + " start"); 30 long startTime = System.currentTimeMillis(); 31 // 模拟一个耗时较长的操作 32 for (;;) 33 { 34 if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) 35 { 36 break; 37 } 38 } 39 } 40 41 System.out.println("Writer has writered down everything! bravo"); 42 } 43 catch (Exception e) 44 { 45 e.printStackTrace(); 46 } 47 48 } 49 50 public void formatFile() 51 { 52 System.out.println("begin to format the file"); 53 // format the file 54 } 55 }
③ 客户端测试代码
1 package com.scl.thread.interrupt; 2 3 public class TestLockInterruptibly 4 { 5 public static void main(String[] args) throws Exception 6 { 7 // 1. 根据lock控制中断 8 // FileHandlerByThreads fileControl = new FileHandlerByThreads(); 9 // Thread readthr = new Thread(new ReadThread(fileControl), "reader"); 10 // Thread writethr = new Thread(new WriteThread(fileControl), "writer"); 11 12 // 2. 使用synchronized关键字控制中断线程 13 SyncFileHandler sync = new SyncFileHandler(); 14 15 Thread readthr = new Thread(new ReadThread(sync), "reader"); 16 Thread writethr = new Thread(new WriteThread(sync), "writer"); 17 writethr.start(); 18 readthr.start(); 19 20 long startTime = System.currentTimeMillis(); 21 // 循环判是否有线程获取到了读锁断 22 while (!sync.isGetReadLock()) 23 { 24 long endTime = System.currentTimeMillis(); 25 // 如果4秒后读线程仍然没有等到读锁,离开等待 26 if (endTime - startTime > 4000) 27 { 28 readthr.interrupt(); 29 System.out.println("4 seconds have passed,try to interrupt reader Thread"); 30 break; 31 } 32 } 33 34 } 35 } 36 37 class ReadThread implements Runnable 38 { 39 private IFileHandler fileControl; 40 41 public ReadThread(IFileHandler fileControl) 42 { 43 this.fileControl = fileControl; 44 } 45 46 @Override 47 public void run() 48 { 49 fileControl.read(); 50 // 测试单纯使用synchronized关键字控制线程中断 51 System.out.println("reader thread end"); 52 fileControl.formatFile(); 53 } 54 } 55 56 class WriteThread implements Runnable 57 { 58 private IFileHandler fileControl; 59 60 public WriteThread(IFileHandler fileControl) 61 { 62 this.fileControl = fileControl; 63 } 64 65 @Override 66 public void run() 67 { 68 fileControl.write(); 69 } 70 }
代码运行结果:线程未中断,控制台输出如下
1 writer start 2 4 seconds have passed,try to interrupt reader Thread
如上面的结果,我们期望的是在读线程在运行4秒后能够被中断,且去运行格式化代码的任务。但是在读线程在调用interrupt方法后,读方法后面的代码并没有执行。反而是一直等待。控制台并没有输出"reader thread end",以及格式化代码的操作。由此可见synchronized关键字不会去响应线程中断。
查看了大部分博客后,发现大家写的都是synchronized并不响应中断。但使用synchronized是否不能完成可中断线程的响应呢?
要接收到中断信息,无非有两种方法 ①等待锁(使用wait、join等方法) ②进入休眠。这两个做法都需要使用循环,让程序等待。第一种方法完全不可行,我现在就是想要什么时候能够获取到锁,JDK通过synchronized没有提供方法让程序员知道:"我的代码获取到锁了吗"这个条件,其次等待对象wait方法,需要在synchrnized里面。synchronized (FileHandlerByThreads.class.getClass())这个条件本来就进不去,更别谈里面的wait方法了。第二种方法,让程序进入休眠。因此有以下代码
1 public void read() 2 { 3 try 4 { 5 System.out.println(Thread.currentThread().getName() + " start"); 6 while (true) 7 { 8 Thread.sleep(100); 9 if (Thread.currentThread().isInterrupted()) 10 { 11 break; 12 } 13 } 14 synchronized (FileHandlerByThreads.class.getClass()) 15 { 16 System.out.println(Thread.currentThread().getName() + " start"); 17 } 18 19 20 } 21 catch (InterruptedException e) 22 { 23 e.printStackTrace(); 24 System.out.println("reader Thread leave the file and going to format the file"); 25 } 26 finally 27 { 28 // lock.unlock(); 29 } 30 31 }
这样写,终于能够获取到interrupt发送过来的信息,并且捕获到异常了。但是,read方法一直都在休眠。这做法不是未卜先知了吗,因为你都知道了read方法肯定是得不到锁的,不断地在休眠。
因此,使用synchronized真的没有方法合理地中断线程的响应。
下面使用ReentrantLock实现可中断线程控制,过程非常简单。把程序稍修改一下就可以了
1 package com.scl.thread.interrupt; 2 3 import java.util.concurrent.locks.ReentrantLock; 4 5 public class FileHandlerByThreads implements IFileHandler 6 { 7 8 private volatile boolean isGetReadLock = false; 9 private ReentrantLock lock = new ReentrantLock(); 10 11 public boolean isGetReadLock() 12 { 13 return isGetReadLock; 14 } 15 16 public void read() 17 { 18 19 try 20 { 21 // 等待20毫秒再进行后续操作,防止主线程操作过快 22 Thread.sleep(50); 23 // 使用reentrantlock 24 lock.lockInterruptibly(); 25 System.out.println(Thread.currentThread().getName() + " start"); 26 isGetReadLock = true; 27 } 28 catch (InterruptedException e) 29 { 30 e.printStackTrace(); 31 System.out.println("reader Thread leave the file and going to format the file"); 32 } 33 // 不能在此处进行锁释放,因为被阻塞的线程可能根本没有获取到锁,若调用unlock方法会抛出IllegalMonitorStateException异常 34 // finally 35 // { 36 // lock.unlock(); 37 // } 38 39 } 40 41 // 模拟运行时间比较久的写操作 42 public void write() 43 { 44 try 45 { 46 47 // 1.使用lock实现写锁定 48 // 等待20毫秒再进行后续操作,防止主线程操作过快 49 Thread.sleep(20); 50 lock.lock(); 51 System.out.println(Thread.currentThread().getName() + " start"); 52 long startTime = System.currentTimeMillis(); 53 // 模拟一个耗时较长的操作 54 for (;;) 55 { 56 if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) 57 { 58 break; 59 } 60 } 61 62 System.out.println("Writer has writered down everything! bravo"); 63 } 64 catch (Exception e) 65 { 66 e.printStackTrace(); 67 } 68 finally 69 { 70 lock.unlock(); 71 } 72 } 73 74 public void formatFile() 75 { 76 System.out.println("begin to format the file"); 77 // format the file 78 } 79 }
1 package com.scl.thread.interrupt; 2 3 public class TestLockInterruptibly 4 { 5 public static void main(String[] args) throws Exception 6 { 7 // 1. 根据lock控制中断 8 FileHandlerByThreads fileControl = new FileHandlerByThreads(); 9 Thread readthr = new Thread(new ReadThread(fileControl), "reader"); 10 Thread writethr = new Thread(new WriteThread(fileControl), "writer"); 11 12 // 2. 使用synchronized关键字控制中断线程 13 // SyncFileHandler sync = new SyncFileHandler(); 14 // Thread readthr = new Thread(new ReadThread(sync), "reader"); 15 // Thread writethr = new Thread(new WriteThread(sync), "writer"); 16 17 writethr.start(); 18 readthr.start(); 19 20 long startTime = System.currentTimeMillis(); 21 // 循环判是否有线程获取到了读锁断 22 while (!fileControl.isGetReadLock()) 23 { 24 long endTime = System.currentTimeMillis(); 25 // 如果4秒后读线程仍然没有等到读锁,离开等待 26 if (endTime - startTime > 4000) 27 { 28 readthr.interrupt(); 29 System.out.println("4 seconds have passed,try to interrupt reader Thread"); 30 break; 31 } 32 } 33 34 } 35 } 36 37 class ReadThread implements Runnable 38 { 39 private IFileHandler fileControl; 40 41 public ReadThread(IFileHandler fileControl) 42 { 43 this.fileControl = fileControl; 44 } 45 46 @Override 47 public void run() 48 { 49 try 50 { 51 fileControl.read(); 52 } 53 catch (InterruptedException e) 54 { 55 // 测试单纯使用synchronized关键字控制线程中断 56 System.out.println("reader thread end"); 57 fileControl.formatFile(); 58 } 59 } 60 } 61 62 class WriteThread implements Runnable 63 { 64 private IFileHandler fileControl; 65 66 public WriteThread(IFileHandler fileControl) 67 { 68 this.fileControl = fileControl; 69 } 70 71 @Override 72 public void run() 73 { 74 fileControl.write(); 75 } 76 }
1 writer start 2 4 seconds have passed,try to interrupt reader Thread 3 reader Thread leave the file and going to format the file 4 java.lang.InterruptedException 5 at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896) 6 at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221) 7 at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340) 8 at com.scl.thread.interrupt.FileHandlerByThreads.read(FileHandlerByThreads.java:24) 9 at com.scl.thread.interrupt.ReadThread.run(TestLockInterruptibly.java:51) 10 at java.lang.Thread.run(Thread.java:744)
需要注意的是FileHandlerByThreads这个类里面的read方法,不能用finally去对lock给解锁。因为被阻塞的读线程基本不可能获取到锁,如果再去释放锁的话会抛出一个java.lang.IllegalMonitorStateException异常。
在总结这随笔之前,本人也有好几个疑问:
① Reentrantlock锁的是什么对象?
synchronized关键字的实现中,本人通过使用锁静态对象的方法把代码块给控制了,但是Reentrantlock 的lock实例根本没有指定任何锁定对象,那锁定的到底是什么。个人认为Reentrantlock根本没有锁定任何东西,因为这个框架底层都是基于CAS去实现的,在底层代码里面也没有发现任何Reentrantlock锁对象的内容。认为锁定的是 Reentrantlock 内置的对象也没关系,因为锁定的内容完全可以不用关心。
② Reentrantlock为什么能够实现可中断的线程响应?
根据上面synchronized关键字实现的中断中,已经知道synchronized不能实现响应式中断的原因是:不能知道线程能否获取到锁,即JDK没有提供代码给程序员使用说:"我的代码获取到锁了吗"这个条件。Reentrantlock 提供了一个获取这个条件的方法:tryLock(),该方法可以测试,代码到底能否获取到锁。个人猜测底层也是通过这个方法去实现响应式中断线程的。
1.1.3 ReentrantLock实现多条件变量的控制
使用synchronized关键字控制线程间的通讯,基本通过wait和notify两个方法把线程给阻塞以及唤醒,来协调两个线程之间的通讯。当使用多个条件的时候,发现使用synchronized很难去实现。例如:生产者-消费者模式中,假如仓库里面的库存已经没法容纳更多的产品,这时候应该调用notify方法把消费者线程唤醒,生产者线程进入休眠。但synchronized的方法没办法通过notify方法唤醒消费者。在调用notify的时候,唤醒的是所有等待锁的线程对象;这时候等待锁的可能是消费者也有可能是生产者,如果唤醒的是生产者,那么生产者又进入了休眠。这样将会导致程序的执行效率比较低。如果有仓库满了,有方法唤醒消费者线程就好了。这时候,ReentrantLock的按对象唤醒就派上用场了;这个也是synchronized处理不了的。
基于这个内容,先看下JDK API 所提供按条件唤醒、休眠的例子。
1 package com.scl.thread.lock.condition; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 class BoundedBuffer 8 { 9 final Lock lock = new ReentrantLock(); 10 // 创建两个不同归属的锁对象 11 final Condition notFull = lock.newCondition(); 12 final Condition notEmpty = lock.newCondition(); 13 // 缓冲区 14 final Object[] items = new Object[100]; 15 int putptr, takeptr, count; 16 17 public void put(Object x) throws InterruptedException 18 { 19 lock.lock(); 20 try 21 { 22 while (count == items.length) 23 // 缓冲区已满,阻塞“生产”线程,通知“消费”线程竞争锁 24 notFull.await(); 25 items[putptr] = x; 26 if (++putptr == items.length) 27 putptr = 0; 28 ++count; 29 // 缓冲区非空,通知“消费”线程竞争锁,继续消费 30 notEmpty.signal(); 31 } 32 finally 33 { 34 lock.unlock(); 35 } 36 } 37 38 public Object take() throws InterruptedException 39 { 40 lock.lock(); 41 try 42 { 43 while (count == 0) 44 // 缓冲区为空,阻塞“消费” 45 notEmpty.await(); 46 Object x = items[takeptr]; 47 if (++takeptr == items.length) 48 takeptr = 0; 49 --count; 50 // 缓冲区未满,通知“生产”线程继续生产。 51 notFull.signal(); 52 return x; 53 } 54 finally 55 { 56 lock.unlock(); 57 } 58 } 59 }
API 中就是使用了condition实现按条件唤醒线程功能。使用一个condition同样能够实现功能,但效率可能不高。
使用ReetrantLock及condition更改进出库内的生产者-消费者模型。
1 package com.scl.thread.lock.condition; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class TestCarPark 7 { 8 public static void main(String[] args) 9 { 10 // 假设车库内有3个车辆使出者,5个使用的.模拟工作五分钟的情况 11 ExecutorService driverInWorkers = Executors.newFixedThreadPool(5); 12 ExecutorService driverOutWorkers = Executors.newFixedThreadPool(3); 13 long startTime = System.currentTimeMillis(); 14 CarPark carPark = new CarPark(10); 15 16 while (true) 17 { 18 19 long endTime = System.currentTimeMillis(); 20 if (endTime - startTime > 2000) 21 { 22 // 程序运行20秒后,不再加任务 23 driverOutWorkers.shutdown(); 24 driverInWorkers.shutdown(); 25 break; 26 } 27 else 28 { 29 try 30 { 31 Thread.sleep(50); 32 } 33 catch (InterruptedException e) 34 { 35 e.printStackTrace(); 36 } 37 // 不断地加减任务 38 driverOutWorkers.submit(new Secute(carPark)); 39 driverInWorkers.submit(new CarOwner(carPark)); 40 } 41 } 42 // 如果线程池任务都已经完成, 则退出线程池 43 if (driverInWorkers.isTerminated() && driverOutWorkers.isTerminated()) 44 { 45 driverOutWorkers.shutdownNow(); 46 driverInWorkers.shutdownNow(); 47 } 48 } 49 }
1 package com.scl.thread.lock.condition; 2 3 //模拟车子类,设置成空 4 public class Car 5 { 6 public Car() 7 { 8 } 9 }
1 package com.scl.thread.lock.condition; 2 3 public class CarOwner implements Runnable 4 { 5 private CarPark carPark; 6 7 public CarOwner(CarPark carPark) 8 { 9 this.carPark = carPark; 10 } 11 12 @Override 13 public void run() 14 { 15 carPark.driverIn(); 16 } 17 }
1 package com.scl.thread.lock.condition; 2 3 public class Secute implements Runnable 4 { 5 private CarPark carPark; 6 7 public Secute(CarPark carPark) 8 { 9 this.carPark = carPark; 10 } 11 12 @Override 13 public void run() 14 { 15 carPark.driverOut(); 16 } 17 }
1 package com.scl.thread.lock.condition; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.locks.Condition; 6 import java.util.concurrent.locks.ReentrantLock; 7 8 /** 9 * 10 * @author scl 11 * 12 * @fileName CarPark.java 13 * 14 * @time 2016下午3:42:35 15 * 16 * declaration: 模拟车库每次进出一辆车子 17 */ 18 public class CarPark 19 { 20 protected int MaxCarNum; 21 private volatile List<Car> carList = new ArrayList<Car>(); 22 private ReentrantLock reLock = new ReentrantLock(); 23 private Condition driverInCon = reLock.newCondition(); 24 private Condition driverOutCon = reLock.newCondition(); 25 26 public CarPark(int maxNum) 27 { 28 this.MaxCarNum = maxNum; 29 } 30 31 public void driverIn() 32 { 33 reLock.lock(); 34 try 35 { 36 // while (true) 37 // { 38 // 39 // if (carList.size() + 1 > MaxCarNum) 40 // { 41 // System.out.println(Thread.currentThread().getName() + 42 // " 当前车库车辆数目:" + carList.size() + "车库满了,不能再入库了"); 43 // 44 // driverInCon.await(); 45 // } 46 // else 47 // { 48 // carList.add(new Car()); 49 // Thread.sleep(300); 50 // System.out.println(Thread.currentThread().getName() + 51 // " 已入库1辆汽车,当前车库车辆数目: " + carList.size()); 52 // // 从这句代码可以看出signal并不释放锁 53 // driverOutCon.signal(); 54 // } 55 // } 56 while (carList.size() + 1 > MaxCarNum) 57 { 58 System.out.println(Thread.currentThread().getName() + " 当前车库车辆数目:" + carList.size() + "车库满了,不能再入库了"); 59 driverInCon.await(); 60 } 61 62 carList.add(new Car()); 63 Thread.sleep(30); 64 System.out.println(Thread.currentThread().getName() + " 已入库1辆汽车,当前车库车辆数目: " + carList.size()); 65 // signal不会 释放锁,也不会唤醒某个线程,只是在condition队列里面把某条线程出列 66 driverOutCon.signal(); 67 68 } 69 catch (Exception e) 70 { 71 e.printStackTrace(); 72 } 73 finally 74 { 75 reLock.unlock(); 76 } 77 78 } 79 80 public void driverOut() 81 { 82 reLock.lock(); 83 try 84 { 85 // while (true) 86 // { 87 // 88 // if (carList.size() - 1 < 0) 89 // { 90 // System.out.println(Thread.currentThread().getName() + 91 // " 车库没有车了,当前车库车辆数目: " + carList.size()); 92 // driverOutCon.await(); 93 // } 94 // else 95 // { 96 // // 使出一辆 97 // carList.remove(0); 98 // Thread.sleep(300); 99 // System.out.println(Thread.currentThread().getName() + 100 // " 已经使出一辆车,当前车库车辆数目:" + carList.size()); 101 // // signal不会 释放锁 102 // driverInCon.signal(); 103 // } 104 // } 105 106 while (carList.size() - 1 < 0) 107 { 108 System.out.println(Thread.currentThread().getName() + " 车库没有车了,当前车库车辆数目: " + carList.size()); 109 driverOutCon.await(); 110 } 111 112 // 使出一辆 113 carList.remove(0); 114 Thread.sleep(30); 115 System.out.println(Thread.currentThread().getName() + " 已经使出一辆车,当前车库车辆数目:" + carList.size()); 116 // signal不会 释放锁,也不会唤醒某个线程,只是在condition队列里面把某条线程出列 117 driverInCon.signal(); 118 119 } 120 catch (Exception e) 121 { 122 e.printStackTrace(); 123 } 124 finally 125 { 126 reLock.unlock(); 127 } 128 } 129 }
在开始写停车场类的时候,代码无意中写错,发现一个问题:signal方法根本不能释放对象锁且不能唤醒任何线程,查看JDK内的notify方法进行了一下比较。API内注明如下:
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程 ;后来搜索了一下他人的博客,发现Condition同样维护着一个队列,当调用await的时候,线程被扔进Condition的队列内,直到调用signal且函数释放了对象锁,才会对线程进行唤醒。相关博客地址:http://www.liuinsect.com/2014/01/27/how_to_understand_condition
同样地,可以对停车场内写错的代码进行使用进行验证该观点。
2. ReentrantReadWriteLock (读写锁)
在使用数据库事务的时候,数据库在处理事务时有两个很重要的锁。一个是读锁,一个是写锁。读锁共享,能跟其他读锁并存;写锁排它,写锁与读锁互斥、写锁与写锁互斥。JDK同样地在控制多线程的时候有这读、写这两把锁。
首先要指出的是ReentrantLock与ReentrantLock没有任何联系。唯一有联系的就是两种不同的锁都是基于AQS进行实现的。
Hibernate 里面有个叫延迟加载的功能,跟读写锁很相似。读的时候,在内存里面查找。如果内存没有,则查询数据库。
1 package com.scl.thread.lock.condition; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.concurrent.locks.ReentrantReadWriteLock; 6 7 public class CacheDemo 8 { 9 // 模拟内存中缓存的数据 10 private Map<String, Object> map = new HashMap<String, Object>(); 11 private ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); 12 13 // 获取对象 14 private Object getObject(String id) 15 { 16 Object data = map.get(id); 17 // 写锁是共享的,可以多条线程进入访问 18 rwlock.readLock().lock(); 19 try 20 { 21 // 如果内存里面该值为空 22 if (data == null) 23 { 24 // 释放写锁 25 rwlock.readLock().unlock(); 26 // 添加写锁 27 rwlock.writeLock().lock(); 28 // 如果某一线程在特定时间点读到数据则不再访问数据库。防止线程重读 29 if (data == null) 30 { 31 try 32 { 33 data = readDataFromDB(id); 34 } 35 catch (Exception e) 36 { 37 e.printStackTrace(); 38 } 39 finally 40 { 41 // 释放写锁,重新上读锁 42 rwlock.writeLock().unlock(); 43 rwlock.readLock().lock(); 44 } 45 } 46 } 47 } 48 catch (Exception e) 49 { 50 e.printStackTrace(); 51 } 52 finally 53 { 54 rwlock.readLock().unlock(); 55 } 56 return data; 57 } 58 59 private Object readDataFromDB(String id) 60 { 61 // 模拟从数据库读取数据 62 return new Object(); 63 } 64 65 }
JDK Api中也有模拟缓存的例子,还用了锁降级。不是很理解锁降级的具体作用。后面具体查找下原因再补充。
1 package com.scl.thread.lock.condition; 2 3 import java.util.concurrent.locks.ReentrantReadWriteLock; 4 5 class CachedData 6 { 7 Object data; 8 volatile boolean cacheValid; 9 ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 10 11 void processCachedData() 12 { 13 rwl.readLock().lock(); 14 if (!cacheValid) 15 { 16 // Must release read lock before acquiring write lock 17 rwl.readLock().unlock(); 18 rwl.writeLock().lock(); 19 // Recheck state because another thread might have acquired 20 // write lock and changed state before we did. 21 if (!cacheValid) 22 { 23 data = getDataFromDB(); 24 cacheValid = true; 25 } 26 // Downgrade by acquiring read lock before releasing write lock 27 rwl.readLock().lock(); 28 rwl.writeLock().unlock(); // Unlock write, still hold read 29 } 30 // use data to do something 31 use(data); 32 rwl.readLock().unlock(); 33 } 34 35 // 模拟调用数据库 36 private Object getDataFromDB() 37 { 38 return new Object(); 39 } 40 41 // 模拟使用数据 42 private void use(Object data) 43 { 44 System.out.println(data.toString()); 45 } 46 }
以上为本次对JDK5上面锁的总结,如有问题,烦请指出纠正。