多线程Lock锁
在JDK1.5以后,在并发包(java.util.concurrent)里面添加包locks,并提供了Lock接口,用于与synchronized类似的锁功能,不同的是Lock需要手动开启锁和释放锁。
为什么要用Lock锁?
- 尝试非阻塞的获取锁
- 获取锁的过程可以被中断
- 超时获取锁
Lock锁的实现类图
Lock锁的常用API
lock():加锁
lockInterruptibly():可中断
tryLock():尝试非阻塞的获取锁
unlock():释放锁
public class AttemptLocking { private ReentrantLock lock = new ReentrantLock(); public void untimed() { boolean captured = lock.tryLock(); try { System.out.println("tryLock(): " + captured); } finally { if(captured) lock.unlock(); } } public void timed() { boolean captured = false; try { captured = lock.tryLock(2, TimeUnit.SECONDS); } catch(InterruptedException e) { throw new RuntimeException(e); } try { System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured); } finally { if(captured) lock.unlock(); } } public static void main(String[] args) { final AttemptLocking al = new AttemptLocking(); al.untimed(); al.timed(); new Thread() { { setDaemon(true); } public void run() { al.lock.lock(); System.out.println("acquired"); } }.start(); Thread.yield(); al.untimed(); al.timed(); } }
在untimed方法里如果tryLock()无法拿到锁则离开去做别的事情;在timed方法里设置了等待时间为2秒,如果等待2秒还没拿到锁则离开去做别的事情。
Lock锁使用模板
Lock lock = new ReentrantLock(); lock.lock(); try { // do my work } finally { lock.unlock(); }
注:这里lock()方法不能写到try里,因为如果自定义的lock发生异常,会导致空放解锁。return要放到try里面,以保证unlock()不会过早发生,从而将数据暴露给了第二个任务。
Lock锁的可重入性
可重入性是指当前线程可以重复进入被自己锁的代码块。如果没有可重入性,当遇到递归操作时就会发生死锁现象。
公平锁和非公平锁
- 公平锁:在多个线程等待锁的时候,当锁被释放时,保证最先请求的线程先得到锁(也就是等待时间最长的线程),保证了每个线程都可以得到锁。
- 非公平锁:与公平锁不一样的是,非公平锁不敢保证每个线程都可以拿到锁,但是可以提高线程交替的效率,从而获得高执行率。
读写锁ReentrantReadWriteLock
ReentrantReadWriteLock可以在多个线程同时进行时,允许一个写线程(不允许其他读线程和写线程)或者多个读线程的操作,支持读多写少的业务场景,提高程序性能。
读写锁模板
public class RwLockTemplate { private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private Lock read = reentrantReadWriteLock.readLock(); private Lock write = reentrantReadWriteLock.writeLock(); public void set(Object params) { this.write.lock(); try { // do my work } finally { this.write.unlock(); } } public Object get() { this.read.lock(); try { // do my work return null; } finally { this.read.unlock(); } } }
Condition接口
既然已经有了Lock锁,那么他也会有一套等待通知机制,他就是Condition接口,对应synchronized的wait()、notify()、notifyAll()方法。
condition模板
public class ConditionTemplete { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void waitc() throws InterruptedException { lock.lock(); try { condition.await(); } finally { lock.unlock(); } } public void notityc() throws InterruptedException { lock.lock(); try { condition.signal(); //condition.signalAll(); } finally { lock.unlock(); } } }
这里await()方法就是等待,signal()/signalAll()方法就是通知,condition可以有多个。
注:这里的signal()/signalAll()方法和synchronized的notify()/notifyAll()方法有所不同,signal()通知这个lock的所有等待,signalAll()通知程序中所有lock的所有等待。
使用Lock和Condition实现阻塞队列
public class BlockingQueueLC<T> { private List<T> queue = new LinkedList<>(); private int limit; private Lock lock = new ReentrantLock(); private Condition needNotEmpty = lock.newCondition(); private Condition needNotFull = lock.newCondition(); public BlockingQueueLC(int limit) { this.limit = limit; } public void push(T el) throws InterruptedException { this.lock.lock(); try { while (this.limit == this.queue.size()) { this.needNotFull.await(); } this.queue.add(el); this.needNotEmpty.signal(); } finally { this.lock.unlock(); } } public T pull() throws InterruptedException { this.lock.lock(); try { while (0 == this.queue.size()) { this.needNotEmpty.await(); } this.needNotFull.signal(); return this.queue.get(0); } finally { lock.unlock(); } } }