1.synchronized
synchronized是一种简单的可重入锁,JDK6开始对synchronized做了大量优化,性能和ReentrantLock差距不大
2.ReentrantLock
ReentrantLock是可重入锁,它可以完全替代synchronized,每次使用都要显示的加锁与解锁,而且提供了新的特性: 公平锁,有条件,可定时,可轮询,可中断,可以有效避免死锁的活跃性问题.
公平锁: 线程按照发出请求的顺序来获取锁,创建ReentrantLock对象时传入true即可生成公平锁
非公平锁: 不必按照发出请求的顺序获取锁,可以插队,创建ReentrantLock时默认为非公平锁
条件:对于每个重入锁,都可以通过newCondition()方法绑定若干个条件对象,不同的线程可以等待不同的条件,从而实现更加细粒度的线程间通信
可轮询与可定时:在获取锁时,先使用tryLock()尝试获取所有的锁,如果不能同时获得,就退回,并重新尝试,休眠时间由一个特定的组件完成
可中断:lockInterruptibly方法能够在获取锁的同时保持对中断的响应
3.ReadWriteLock(读写锁)
ReentrantReadWriteLock实现了ReadWriteLock接口,构造器提供了公平锁与非公平锁两种创建方式,对于读多写少的场景,如果全部使用排它锁显然是没有必要的,因为不同的读操可以并发执行提高效率,如果使用排它锁则读操作会阻塞其他读操作降低效率,此时使用读写锁是更好的选择
读锁(共享锁):允许多个读线程并发执行
写锁(排他锁):不允许写线程与其他线程(读或者写线程)并发执行
读锁的升级:读锁升级为写锁,ReentrantReadWriteLock不支持锁升级
写锁的降级:持有写锁的同时去获取读锁,然后释放写锁
- 写锁降级的简单介绍
首先要了解写锁的特点,线程间是互斥的,但是如果是当前线程则可重入,因此仅仅对当前线程而言,可以在获取到写锁以后再获取读锁,然后再释放写锁
- 通过简化实例简单了解锁降级
//主要代码
public class Degrade {
volatile int value = 0;
public void getAndSetValue(){
Thread.sleep(100); //便于测试
value++; //写操作
Thread.sleep(100); //便于测试
System.out.println(value); //读操作
}
public void getValue(){
Thread.sleep(100); //便于测试
System.out.println("get:"+value); //读操作
}
}
//测试代码
@Test
public void test(){
Degrade deGrade = new Degrade();
for(int i=0;i<2;i++){ //创建2个读写线程
new Thread(new Runnable() {
@Override
public void run() {
for(;;){
deGrade.getAndSetValue();
}
}
}).start();
}
for (int i=0;i<5;i++){ //创建5个读线程
new Thread(new Runnable() {
@Override
public void run() {
for (;;){
deGrade.getValue();
}
}
}).start();
}
现在我们要完成每一次value值更新,都会至少被一个读操作读取到,显然不加锁的情况下,是不行的
测试方式1,改写如下
//主要代码 public class Degrade { volatile int value = 0; final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); Lock read = rwlock.readLock(); Lock write = rwlock.writeLock(); public void getAndSetValue(){ Thread.sleep(100); //便于测试 write.lock(); value++; //写操作 write.unlock(); //写锁释放 Thread.sleep(100); //便于测试 read.lock(); System.out.println(value); //读操作 read.unlock(); } public void getValue(){ Thread.sleep(100); //便于测试 read.lock(); System.out.println("get:"+value); //读操作 read.unlock(); } } 输出结果片段: 2 get:2 get:4 //显然此时不能满足条件,因为当写锁释放时,此时可能由其他读写线程先执行,这样value刚更改过还没有被读取输出过就又没修改了,因此输出结果不续性
- 测试方式2,改写如下
```
//主要代码
public class Degrade {
volatile int value = 0;
final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
Lock read = rwlock.readLock();
Lock write = rwlock.writeLock();
public void getAndSetValue(){
Thread.sleep(100); //便于测试
write.lock();
value++; //写操作
Thread.sleep(100); //便于测试
System.out.println(value); //读操作
write.unlock();
}
public void getValue(){
Thread.sleep(100); //便于测试
read.lock();
System.out.println("get:"+value); //读操作
read.unlock();
}
}
输出结果为:
1
2
get:2
get:2
get:2
get:2
get:2
3
get:3
get:3
get:3
4
get:4
get:4
//可以看到满足要求,输出结果连续,但是这种方式,将一个读操作也放入写锁中,每次写操作完成后必定执行此读操作,从而实现每次更新value值后,都至少有一个读操作读取到value值输出
//现在如果要写操作执行完成后,其他读操作都有机会先执行,该怎么实现?
```
- 测试方式3(写锁降级),修改如下
~~~
//主要代码
public class Degrade {
volatile int value = 0;
final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
Lock read = rwlock.readLock();
Lock write = rwlock.writeLock();
public void getAndSetValue(){
Thread.sleep(100); //便于测试
write.lock(); //获取写锁
value++; //写操作
read.lock(); //已经有写锁的情况下可直接获取到读锁(无需竞争)
write.unlock(); //释放写锁,此时锁降级为读锁(其他读线程可以并行)
Thread.sleep(100); //便于测试
System.out.println(value); //读操作
read.unlock(); //释放读锁
}
public void getValue(){
Thread.sleep(100); //便于测试
read.lock();
System.out.println("get:"+value); //读操作
read.unlock();
}
}
输出结果:
1
get:2
get:2
get:2
get:2
get:2
get:2
2
get:2
get:2
get:2
get:2
3
get:3
get:3
get:4
get:4
get:4
4
get:4
get:4
//可以看到写操作执行完成之后,其他的读操作都有机会先执行,因为写操作完成后写锁降级为读锁,此时所有读线程都有机会抢到cpu执行权,例如先输出get:4,后输出4
~~~
显然通过上面的对比可以发现,写锁降级为读锁后,其他读锁都可以并发执行了,从侧面提升了读的效率,同时也使读线程可以更加敏感的感知到数据的更新