在这里,我先介绍一些锁的相关概念介绍:
1、可重入锁
如果锁具备可重入性,则称作为 可重入锁 。像 synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了 锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代码中的两个方法method1和method2都用synchronized修饰了。假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是,这就会造成死锁,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
2、可中断锁
顾名思义,可中断锁就是可以响应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示tryLock(long time, TimeUnit unit)和lockInterruptibly()的用法时已经体现了Lock的可中断性。
3、公平锁
公平锁即 尽量 以请求锁的顺序来获取锁。比如,同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。而非公平锁则无法保证锁的获取是按照请求锁的顺序进行的,这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁
4、独享锁
独享锁是指该锁一次只能被一个线程所持有。对于Java 中的ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其写锁是独享锁。当然Synchronized也是独享锁。
简介:在高并发场景中,我们经常用到java提供的关键字synchronized或者concurrents包中实现了Lock接口的ReentrantLock。但是他们都是独享锁,在同一时刻只有一个线程能够获取锁。如果是读多写少的场景,依然使用独享锁的话,很显然这将是出现性能瓶颈的地方。java还提供ReadWriteLock接口,它不仅包含上述锁的特性,他还有一个特殊的名称叫做读写锁。为什么呢?因为他主要有两种方法readLock()和writeLock()方法,管理着一个读锁(如果没有线程锁定ReadWriteLock进行写入,则多线程可以访问读锁)和一个写锁(如果没有线程正在读或写,那么一个线程可以访问写锁)。Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口。
ReetrantReadWriteLock类特性:
- 支持可选的公平政策。
非公平模式(默认)
当被构造为不公平(默认)时,进入读写锁定的顺序是未指定的,受到重入限制。 持续竞争的非空格锁可能无限期地推迟一个或多个读卡器或写入器线程,但通常具有比公平锁定更高的吞吐量。
公平模式
当公平地构建时,线程使用近似到达订单策略来争取进入。 当释放当前持有的锁时,最长等待的单个写入器线程将被分配写入锁定,或者如果有一组读取器线程等待比所有等待写入器线程长的时间,则该组将被分配读取锁定。 - 可重入
- 锁定降级 -允许写锁升级到读锁。 但是,从读锁定升级到写锁是不可能的。
- 中断锁获取 -读锁和写锁在锁定采集期间都支持中断。
- Condition支持 (后续介绍)
示例:
这里我们编写一段编写一段小程序来检验readlock()获取读锁定和writeLock()获取写锁定。
public class TestReadWriteLock {
//公平模式
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private static String message = "a";
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(new WriterA());
t1.setName("Wtiter A");
Thread t2 = new Thread(new WriteB());
t2.setName("Write B");
Thread t3 = new Thread(new Reader());
t3.setName("Reader1");
Thread t4 = new Thread(new Reader());
t4.setName("Reader2");
t1.start();
t2.start();
t3.start();
t4.start();
t1.join();
t2.join();
t3.join();
t4.join();
}
static class Reader implements Runnable{
@Override
public void run() {
//查询写锁是否由任何线程持有
if(lock.isWriteLocked()){
System.out.println(Thread.currentThread().getName()+" Find Write Lock Present");
}
lock.readLock().lock();
try {
Long duration =(long) (Math.random()*10000);
System.out.println(Thread.currentThread().getName()
+" Time Taken " + (duration/1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+": "+message);
lock.readLock().unlock();
}
}
}
static class WriterA implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
lock.writeLock().lock();
try {
Long duration = (long)(Math.random()*10000);
System.out.println(Thread.currentThread().getName()
+" Time Taken " + (duration/1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
message = message.concat("a");
lock.writeLock().unlock();
}
}
}
static class WriteB implements Runnable{
@Override
public void run() {
lock.writeLock().lock();
try {
Long duration = (long)(Math.random()*10000);
System.out.println(Thread.currentThread().getName()
+" Time Taken " + (duration/1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
message = message.concat("b");
lock.writeLock().unlock();
}
}
}
}
结果: