在Java5.0之前,在协调对象的访问时可以使用的机制只有synchronized和volatile。Java5.0增加了一种新的机制:ReentrantLock。与之前提到过的机制相反。ReentrantLock并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。
与内置加锁机制不同的是,Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显示的。在Lock的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性方面可以有所不同。
Lock lock = new ReentranLock(); ... lock.lcck; try{ //更新对象状态 //捕获异常,并在必要时恢复不变性条件 }finally{ lock.unlock; } |
package com.mylearn.thread.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by IntelliJ IDEA. * User: yingkh * Date: 12-12-25 * Time: 上午10:52 * CopyRight:360buy * Descrption: * To change this template use File | Settings | File Templates. */ public class ReentranLockTest { public static void main(String args[]) { Lock lock = new ReentrantLock(); WorkerOne workerOne = new WorkerOne(lock); workerOne.setName("WorkerOne"); WorkerTwo workerTwo = new WorkerTwo(lock); workerTwo.setName("WorkerTwo"); workerTwo.start(); //两个线程用同一个锁 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } workerOne.start(); } } class WorkerOne extends Thread { private Lock lock; public Lock getLock() { return lock; } public void setLock(Lock lock) { this.lock = lock; } WorkerOne(Lock lock) { this.lock = lock; } public void run() { try { System.out.println(Thread.currentThread().getName() + ":step into critical section,try"); lock.lock(); System.out.println(Thread.currentThread().getName() + ":step into critical section,begin"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } System.out.println(Thread.currentThread().getName() + ":step into critical section,end"); } finally { lock.unlock(); } } } class WorkerTwo extends Thread { private Lock lock; public Lock getLock() { return lock; } public void setLock(Lock lock) { this.lock = lock; } WorkerTwo(Lock lock) { this.lock = lock; } private void sayHello() { lock.lock(); //不同的方法用同一个锁,可以重入 try { System.out.println(Thread.currentThread().getName() + ":call say Hello()"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } finally { lock.unlock(); } } public void run() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + ":step into critical section,begin"); sayHello(); //测试重入锁 System.out.println(Thread.currentThread().getName() + ":step into critical section, end"); } finally { lock.unlock(); } } } |
结果:
WorkerTwo:step into critical section,begin WorkerTwo:call say Hello() WorkerOne:step into critical section,try WorkerTwo:step into critical section, end WorkerOne:step into critical section,begin WorkerOne:step into critical section,end |
可见workerone和workerTwo用的是同一个锁,WorkerTwo中的sayHello方法和run方法用的也是同一个锁。
互斥性:WorkerTwo拿到锁后,WorkerOne只能等待
可重入性:WorkerTwo的run方法拿到锁后,调用sayHello方法还是可以进去的。
package com.mylearn.thread.lock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Created by IntelliJ IDEA. * User: yingkh * Date: 12-12-25 * Time: 下午1:52 * CopyRight:360buy * Descrption: 读写重入锁 * * * 1. WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。 2. WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持ReadLock的持有,反过来ReadLock想要升级为WriteLock则不可能。 3. ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。 4. 不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。 5. WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。 * To change this template use File | Settings | File Templates. */ public class ReentrantReadWriteLockSample { public static void main(String args[]) { testReadLock(); // testWriteLock(); } /** * 开启三个线程,两个读线程,一个写线程 ,可以看到get方法几乎同时显示,儿put中的打印结果则要等上将近5秒左右 * 这就说明了,ReadLock可以多线程持有并且排斥WriteLock的持有线程。 */ public static void testReadLock() { final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport(); support.initCache(); Runnable runnable = new Runnable() { public void run() { support.get("test"); } }; new Thread(runnable).start(); new Thread(runnable).start(); new Thread(new Runnable() { public void run() { support.put("test", "test"); } }).start(); } /** * 开启三个线程,两个写线程,一个读线程。执行结果是三个打印结果都是将近5s的时间间隔,这说明了WriteLock是独占的 */ public static void testWriteLock() { final ReadWriteLockSampleSupport support = new ReadWriteLockSampleSupport(); support.initCache(); new Thread(new Runnable() { public void run() { support.put("key1", "value1"); } }).start(); new Thread(new Runnable() { public void run() { support.put("key2", "key2"); } }).start(); new Thread(new Runnable() { public void run() { support.get("key1"); } }).start(); } } class ReadWriteLockSampleSupport { private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private final Lock readLock = reentrantReadWriteLock.readLock(); private final Lock writeLock = reentrantReadWriteLock.writeLock(); private volatile boolean completed; private Map public void initCache() { readLock.lock(); if (!completed) { // 在获取写锁之前必须释放读锁 readLock.unlock(); writeLock.lock(); if (!completed) { cache = new HashMap completed = true; } //在释放写锁之前,通过获取读锁降级 readLock.lock(); writeLock.unlock(); } System.out.println("empty? " + cache.isEmpty()); readLock.unlock(); } public String get(String key) { readLock.lock(); System.out.println(Thread.currentThread().getName() + "read."); startTheCountdown(); try { return cache.get(key); } finally { readLock.unlock(); } } public String put(String key, String value) { writeLock.lock(); System.out.println(Thread.currentThread().getName() + "write:"); startTheCountdown(); try { return cache.put(key, value); } finally { writeLock.unlock(); } } /** * 延迟5秒 */ private void startTheCountdown() { long currentTime = System.currentTimeMillis(); for (; ; ) { long diff = System.currentTimeMillis() - currentTime; if (diff > 5000) { break; } } } } |
Synchronized:
缺点
1. 内置锁无法中断一个正在等待获取锁的线程,或者无法在请求获取一个锁时无限地等待下去。
2. 无法实现非阻塞的加锁规则
3. 死锁的问题比较麻烦,防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。
优点:
1. 必须在获取该锁的代码块中释放,这就简化了编码工作,并且与异常处理操作实现了很好的交互。
2. 为许多开发人员所熟知,简洁紧凑
3. 在线程转储中能给出在哪些调用帧中获得了哪些锁,并能够检测和识别发生死锁的线程。
4. 未来更可能会提升synchronized而不是ReentrantLock的性能。因为synchronized是JVM的内置属性,它能执行一些优化,例如对线程封闭的锁对象执行锁消除,通过增加锁的粒度来消除内置锁的同步。而如果通过基于类库的锁来实现这些功能,则可能性不大。
5. Java5上Lock比synchronized的性能大很多,但是java6.0之后已经相差无几了,仅当内置锁不能满足需求时,才可以考虑使用Lock。
Lock:
缺点:
1. 使用比内置锁复杂,必须在finally中释放锁,否则,如果在被保护的代码块抛出了异常,则这个锁永远无法释放。
优点:
1. tryLock可以实现可定时与可轮询的锁获取模式,具有更完善的错误恢复机制。
2. tryLock可以有效防止死锁的发生。
3. Lock可以带有时间限制
4. Lock可以被中断,lockInterruptibly
5. 锁可以通过分段技术提高性能。
6. 锁可以选择公平性,非公平的锁的性能往往高于公平锁,原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。
7. 提供读-写锁,对于一些数据结构能够大大提升性能。
总结:与内置锁相比,显示的Lock提供了一些扩展功能,在处理锁的不可用性方面有着更高的灵活性,并且对队列行有着更好的控制。但ReentrantLock不能完全代替synchronized,只有在synchronized无法满足需求时,才应该使用它。这些高级功能包括: 可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。