锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。
在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。
虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Java并发包中的显式锁接口和类位于包java.util.concurrent.locks下,主要接口和类有:
❑ 锁接口Lock,主要实现类是ReentrantLock;
❑ 读写锁接口ReadWriteLock,主要实现类是ReentrantReadWriteLock。
Lock接口
显式锁接口Lock的定义为:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
1)lock()/unlock():就是普通的获取锁和释放锁方法,lock()会阻塞直到成功。
2)lockInterruptibly():与lock()的不同是,它可以响应中断,如果被其他线程中断了,则抛出InterruptedException。
3)tryLock():只是尝试获取锁,立即返回,不阻塞,如果获取成功,返回true,否则返回false。
4)tryLock(long time, TimeUnit unit):先尝试获取锁,如果能成功则立即返回true,否则阻塞等待,但等待的最长时间由指定的参数设置,在等待的同时响应中断,如果发生了中断,抛出InterruptedException,如果在等待的时间内获得了锁,返回true,否则返回false。
5)newCondition:新建一个条件,一个Lock可以关联多个条件。
可以看出,相比synchronized,显式锁支持以非阻塞方式获取锁、可以响应中断、可以限时,这使得它灵活得多。
重入锁ReentrantLock
Lock接口的主要实现类是ReentrantLock,它的基本用法lock/unlock实现了与syn-chronized一样的语义,包括:
❑ 可重入,一个线程在持有一个锁的前提下,可以继续获得该锁;
❑ 可以解决竞态条件问题;
❑ 可以保证内存可见性。
ReentrantLock有两个构造方法:
public ReentrantLock() public ReentrantLock(boolean fair)
参数fair表示是否保证公平,不指定的情况下,默认为false,表示不保证公平。所谓公平是指,等待时间最长的线程优先获得锁。保证公平会影响性能,一般也不需要,所以默认不保证,synchronized锁也是不保证公平的
使用显式锁,一定要记得调用unlock。一般而言,应该将lock之后的代码包装到try语句内,在finally语句内释放锁。比如,使用ReentrantLock实现Counter,代码可以为:
public class Counter { private final Lock lock = new ReentrantLock(); private volatile int count; public void incr() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { return count; } }
读写锁ReentrantReadWriteLock
ReadWriteLock仅定义了获取读锁和写锁的两个方法,即readLock()方法和writeLock()方法
ReentrantReadWriteLock使用方式如下:
public class Cache { private static final Map<String, Object> map = new HashMap<String, Object>(); private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private static final Lock r = rwl.readLock(); private static final Lock w = rwl.writeLock(); public static final Object get(String key) { r.lock(); try { return map.get(key); } finally { r.unlock(); } } public static final Object put(String key, Object value) { w.lock(); try { return map.put(key, value); } finally { w.unlock(); } } public static final void clear() { w.lock(); try { map.clear(); } finally { w.unlock(); } } }
也就是说,当使用 ReadWriteLock 的时候,并不是直接使用,而是获得其内部的读锁和写锁,然后分别调用lock/unlock。
从表面来看,ReadLock和WriteLock是两把锁,实际上它只是同一 把锁的两个视图而已。什么叫两个视图呢?可以理解为是一把锁,线程分成两类:读线程和写线程。读线程和读线程之间不互斥(可以同时拿到这把锁),读线程和写线程互斥,写线程和写线程也互斥。
参考: Java编程的逻辑 16.2 显式锁
Java并发编程的艺术 5.1 Lock接口
Java并发实现原理 3.1 互斥锁