一重入锁(ReentrantLock)
Java SE5以后,Java并发包基于Lock接口,实现了高性能的支持重入的锁ReentrantLock。重入这里指的是在某线程已经获取锁之后,该线程可以再次获取锁,进入同步代码块。这里需要强调一下重入的概念中所指的线程是已经获得锁的的线程,这与线程安全不冲突,因为只有一个线程可以获取锁,也就是说始终都是同一个线程(获取锁的线程)在运行同步代码,相当于单线程运行,当然是线程安全的。synchronized关键字也是支持重入的,例如synchronized可以在递归调用中使用。
以下列出了synchronized与ReentrantLock的不同之处:
- ReentrantLock提供了显式加解锁操作。提供了lock(),unlock()方法进行加解锁的操作,而synchronized是隐式进行加锁与解锁操作(依赖于编译器将其编译为moniterenter与moniterexit)。
- 对锁的等待可以中断,在持有锁的线程长时间不释放锁时,等待锁的线程可以选择放弃等待,这样就避免了synchronized可能带来的死锁问题。ReentrantLock.tryLock()可以设置等待时间。
- ReentrantLock提供了公平锁与非公平锁,而synchronized的实现是非公平锁。
ReentrantLock相对于synchronized来说一般用于,加锁与解锁操作需要分离的使用场景,例如加解锁不再一个函数里(synchronized无法用括号包围),相对来说ReentrantLock提供了更高的灵活性,但是使用时一定不要忘了释放锁。
二读写锁(ReentrantReadWriteLock)
加锁是我们为了保证共享资源在多线程操作下不会出现脏读、脏存现象,因此需要对共享资源进行了加锁保护。但是在大部分系统中,变量的读操作远远多于变量的写操作,大多情况下变量的值都是不变的,如果我们可以去除锁,并保证变量的改变能对所有线程可以及时可见,那么也不会存在脏读现象,这样就可以大幅度的提升系统的并发性能。读写锁正是基于上述需求产生的,相对与ReentrantLock它对变量的读写操作进行了区别对待,遵循以下特性:
- 在没有写操作线程获取写锁的情况下,所有读操作都可以获取读锁。
- 在有写线程获取写锁的情况下,读操作等待写线程释放锁后,才可以获取读锁。
- 在有读线程获取读锁的情况下,写线程会等待所有读线程释放锁后,才可以获取写锁,并且与此同时,所有的读锁也不可获取。
综上所述,就是保证读写不会同时发生。下面我们通过开发一个线程安全的读写分离HashMap来看看读写锁的具体使用:
public class ReentrantReadWriteLockHashMap { private final Map<String, Object> hashMap = new HashMap<String, Object>(); private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); public final void put(String key, Object value){ //上写锁,不允许其他线程读也不允许写 writeLock.lock(); hashMap.put(key, value); writeLock.unlock(); } public final Object get( String key ){ //上读锁,其他线程只能读不能写 readLock.lock(); Object value = hashMap.get(key); readLock.unlock(); return value; } }
通过示例代码,可以看到读写锁的使用还是很简单的,关键要理解读写锁的运行机制,读写分离,区别对待。
到此,对这个题目讲解就结束了,你应该对Java中的锁有所了解了吧。