在开发Java多线程应用程序中,各个线程之间由于要共享资源,必须用到锁机制。Java提供了多种多线程锁机制的实现方式,常见的有synchronized、ReentrantLock、Semaphore、AtomicInteger等。每种机制都有优缺点与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时得心应手。
更多Java锁机制的详细介绍参见文档《Java锁机制详解》。
几乎每一个Java开发人员都认识synchronized,使用它来实现多线程的同步操作是非常简单的,只要在需要同步的对方的方法、类或代码块中加入该关键字,它能够保证在同一个时刻最多只有一个线程执行同一个对象的同步代码,可保证修饰的代码在执行过程中不会被其他线程干扰。使用synchronized修饰的代码具有原子性和可见性,在需要进程同步的程序中使用的频率非常高,可以满足一般的进程同步要求(详见《Java多线程基础》)。
synchronized实现的机理依赖于软件层面上的JVM,因此其性能会随着Java版本的不断升级而提高。事实上,在Java1.5中,synchronized是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。到了Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.7与1.8中,均对该关键字的实现机理做了优化。
需要说明的是,当线程通过synchronized等待锁时是不能被Thread.interrupt()中断的,因此程序设计时必须检查确保合理,否则可能会造成线程死锁的尴尬境地。
最后,尽管Java实现的锁机制有很多种,并且有些锁机制性能也比synchronized高,但还是强烈推荐在多线程应用程序中使用该关键字,因为实现方便,后续工作由JVM来完成,可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时,才考虑使用其他机制,如ReentrantLock等。
可重入锁,顾名思义,这个锁可以被线程多次重复进入进行获取操作。ReentantLock继承接口Lock并实现了接口中定义的方法,除了能完成synchronized所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
Lock实现的机理依赖于特殊的CPU指定,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准,因此我们建议在高并发量情况下使用ReentrantLock。
ReentrantLock引入两个概念:公平锁与非公平锁。公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁。反之,JVM按随机、就近原则分配锁的机制则称为不公平锁。ReentrantLock在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。这是因为,非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
ReentrantLock通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作。通常使用方式如下所示:
1 Lock lock = new ReentrantLock(); 2 try { 3 lock.lock(); 4 //...进行任务操作 5 } finally { 6 lock.unlock(); 7 }
下面我们详细介绍有关ReentrantLock提供的可响应中断锁、可轮询锁请求、定时锁等机制与操作方式。
1、线程在等待资源过程中需要中断
ReentrantLock的在获取锁的过程中有2种锁机制,忽略中断锁和响应中断锁。当等待线程A或其他线程尝试中断线程A时,忽略中断锁机制则不会接收中断,而是继续处于等待状态;响应中断锁则会处理这个中断请求,并将线程A由阻塞状态唤醒为就绪状态,不再请求和等待资源。
lock.lock()可设置锁机制为忽略中断锁,lock.lockInterruptibly()可设置锁机制为响应中断锁。下述例子描述了,一个写线程和一个读线程分别操作同一个同一个对象的写方法和读方法,写方法需要执行10秒时间,主线程中在启动写线程writer和读线程reader后,启动了第三个线程,这个线程判断当程序执行5秒后,如果读线程依然处于等待状态,就将他中断,不再继续等待资源。
1 import java.util.concurrent.locks.ReentrantLock; 2 3 public class ReentrantLockInterrupt { 4 public static void main(String[] args) { 5 MyBuffer buffer = new MyBuffer(); 6 7 //开启写线程 8 final WriteThread write = new WriteThread(buffer); 9 write.start(); 10 11 //开启读线程 12 final ReadThread read = new ReadThread(buffer); 13 read.start(); 14 15 //开启第三个线程,用于监听并中断读线程 16 new Thread(new Runnable() { 17 @Override 18 public void run() { 19 long readThreadMaxWaitTime = 5000; //读线程最大等待时间,单位:毫秒 20 long startTime = System.currentTimeMillis(); 21 while(System.currentTimeMillis()-startTime<readThreadMaxWaitTime){} 22 System.out.println("读线程等待时间已超过"+readThreadMaxWaitTime/1000+"秒,请求中断...."); 23 read.interrupt(); 24 } 25 }).start(); 26 } 27 } 28 29 class WriteThread extends Thread{ 30 private MyBuffer buffer; 31 public WriteThread(MyBuffer buffer){ 32 this.buffer = buffer; 33 } 34 @Override 35 public void run() { 36 buffer.write(); 37 } 38 } 39 40 class ReadThread extends Thread{ 41 private MyBuffer buffer; 42 public ReadThread(MyBuffer buffer) { 43 this.buffer = buffer; 44 } 45 @Override 46 public void run() { 47 try { 48 buffer.read(); 49 } catch (InterruptedException e) { 50 System.out.println("读线程已经被中断....."); 51 } 52 } 53 } 54 55 class MyBuffer { 56 //使用ReentrantLock锁 57 private ReentrantLock lock = new ReentrantLock(); 58 59 //写操作 60 public void write(){ 61 //lock操作必须放在此处,放于try内就会报错,为什么??? 62 lock.lock(); 63 try { 64 long writeNeedTime = 10000; //写操作需要时间,单位:毫秒 65 long writeStartTime = System.currentTimeMillis(); 66 System.out.println("写操作开始,预计执行时间:"+writeNeedTime/1000+"秒...."); 67 while(System.currentTimeMillis()-writeStartTime<writeNeedTime){} 68 System.out.println("写操作完成...."); 69 } finally { 70 lock.unlock(); 71 } 72 } 73 74 //读操作 75 public void read() throws InterruptedException { 76 //lock()方法设置锁机制为“忽略中断锁”,当调用此方法的线程自身或被其他线程请求中断(interrupt)时,操作线程不响应请求,继续处于等待状态 77 //lockInterruptibly()方法可设置锁机制为“相应式中断锁”,当调用此方法的线程自身或被其他线程请求中断(interrupt)时,线程会相应请求,并在调用当前方法的操作时中断线程,中断后不操作线程后续任务 78 //以上的响应指的是线程正在获取锁的过程中被请求中断,若线程在其他非阻塞与阻塞状态时被请求中断,lockInterruptibly()是无法响应中断的, 79 //非阻塞状态可根据中断标记位Thread.currentThread().isInterrupted(),阻塞状态可通过抛出异常InterruptedException来中断线程 80 //详细可以参考http://www.cnblogs.com/hanganglin/articles/3517178.html中的Thread.interrupt资料 81 lock.lockInterruptibly(); 82 try { 83 System.out.println("读操作完成...."); 84 } finally { 85 lock.unlock(); 86 } 87 } 88 }