可重入锁是一种排他锁,同一时间只允许一个线程操作竞争资源。读写锁是针对读、写场景设计的,允许多个线程同时持有锁。读写锁维护了一个读锁和一个写锁。其机制如下:
- 没有其它线程占用写锁的情况下,同一时间可以有多个线程加读锁。
- 没有任意线程占用读锁的情况下, 同一时间只有一个线程可以加写锁。
简单总结就是要么读,要么写,允许多个线程同时读,只允许一个线程单独写。看看源码。
public interface ReadWriteLock { | |
Lock readLock(); | |
Lock writeLock(); | |
} |
原来这是一个接口,它本身并不是锁对象,只是维护了一个读锁(readLock),一个写锁(writeLock)。其实现类是ReentrantReadWriteLock
.
public class Demo16 { | |
public static void main(String[] args) { | |
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); | |
readWriteLock.readLock().lock(); | |
new Thread(readWriteLock.readLock()::lock).start(); | |
} | |
} |
上面程序可以正常退出,因为两个线程可以同时获取读锁,并不会hang住。
public class Demo16 { | |
public static void main(String[] args) { | |
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); | |
readWriteLock.writeLock().lock(); | |
new Thread(readWriteLock.readLock()::lock).start(); | |
} | |
} |
上面代码则不能正常退出,因为主线程使用了写锁,被创建的子线程不允许获取读锁。
ReentrantReadWriteLock
不仅拥有读、写锁的功能,还保留了写锁的可重入锁、公平|非公平锁的机制。
public class Demo17 { | |
public static void main(String[] args) { | |
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); | |
readWriteLock.writeLock().lock(); | |
readWriteLock.writeLock().lock(); | |
new Thread(() -> { | |
System.out.println("thread2 try to lock"); | |
readWriteLock.writeLock().lock(); | |
System.out.println("thread2 lock successfully"); | |
}).start(); | |
readWriteLock.writeLock().unlock(); | |
System.out.println("thread1 unlock one time"); | |
readWriteLock.writeLock().unlock(); | |
System.out.println("thread2 unlock twice"); | |
} | |
} |
输出为。
thread1 unlock one time | |
thread2 try to lock | |
thread2 unlock twice | |
thread2 lock successfully |
注意读锁不具备上述机制,因为它根本不是排他锁。公平锁的简单演示如下。
public class Demo18 { | |
public static void main(String[] args) { | |
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); | |
Runnable action = () -> { | |
System.out.println("thread" + Thread.currentThread().getName() + "try to lock"); | |
lock.writeLock().lock(); | |
System.out.println("thread" + Thread.currentThread().getName() + " lock successfully"); | |
lock.writeLock().unlock(); | |
}; | |
for (int i = 0; i < 10; i++) { | |
new Thread(action).start(); | |
} | |
} | |
} |
输出如下。
threadThread-0try to lock | |
threadThread-2try to lock | |
threadThread-1try to lock | |
threadThread-0 lock successfully | |
threadThread-2 lock successfully | |
threadThread-3try to lock | |
threadThread-1 lock successfully | |
threadThread-4try to lock | |
threadThread-3 lock successfully | |
threadThread-5try to lock | |
threadThread-6try to lock | |
threadThread-4 lock successfully | |
threadThread-5 lock successfully | |
threadThread-6 lock successfully | |
threadThread-8try to lock | |
threadThread-7try to lock | |
threadThread-8 lock successfully | |
threadThread-7 lock successfully | |
threadThread-9try to lock | |
threadThread-9 lock successfully |
真的很简单。下面讲点有意思的:锁降级和锁升级。先回顾下我们对读写锁机制的描述:
- 没有其它线程占用写锁的情况下,同一时间可以有多个线程加读锁。
发现没有,我们用的是其它,而不是任意。说人话就是,如果线程A持有写锁,其它线程就不允许持有读锁,A线程却可以。
public class Demo19 { | |
public static void main(String[] args) { | |
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); | |
lock.writeLock(); | |
lock.readLock(); | |
System.out.println("get read lock..."); | |
} | |
} |
输出如下。
get read lock...
不过上面的例子中写锁与读锁的顺序不能反。再看之前我的概念也是这么写的。
- 没有任意线程占用读锁的情况下, 同一时间只有一个线程可以加写锁。
为什么呢?其实是因为写锁可以降级为读锁。换句话说,我本来是写锁,看见是自己人来了(同一个线程想获取读锁),我把自己降级读锁,允许你进来和我玩。但是如果是读锁,即使我看见自己人来了,我也没有办法升级成为写锁阿。毕竟降级简单升级难。
看下列代码。
public class Demo20 { | |
public static void main(String[] args) throws InterruptedException { | |
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); | |
readWriteLock.writeLock().lock(); | |
readWriteLock.readLock().lock(); | |
new Thread(() -> { | |
System.out.println("thrad2 to get read lock"); | |
readWriteLock.readLock().lock(); | |
System.out.println("thrad2 get read lock succussfully"); | |
}).start(); | |
TimeUnit.SECONDS.sleep(1); | |
System.out.println("thread1 unlock write lock"); | |
readWriteLock.writeLock().unlock(); | |
} | |
} |
结果是。
thrad2 to get read lock | |
thread1 unlock write lock | |
thrad2 get read lock succussfully |
原来写锁的降级只是一瞬间,就是在持有写锁的情况下同一线程想获取读锁的那一瞬间。