个人博客:www.xiaobeigua.icu
synchronized 内部锁与 ReentrantLock 锁都是独占锁(排它锁), 同一 时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。
ReentrantReadWriteLock 读写锁 是一种改进的排他锁,也可以称作 共享/排他锁。允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新。
读写锁通过读锁与写锁来完成读写操作,线程在读取共享数据前必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的。
线程在修改共享数据前必须先持有写锁,写锁是排他的,一个线程持有写锁时其他线程无法获得相应的锁,读锁只是在读线程之间共享,任何一个线程持有读锁时,其他线程都无法获得写锁, 保证线程在读取数据期间没有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改,这也保证了数据共享之间的可见性。
锁类型 | 获得条件 | 排他性 | 作用 |
读锁 | 写锁未被任意线程持有 | 对读线程是共享的,对写线程是排他的 | 允许多个读线程可以同时读 取共享数据,保证在读共享数 据时,没有其他线程对共享数 据进行修改 |
写锁 | 该写锁未被其他线程持 有,并且相应的读锁也未被其他线程持有 | 对读线程或者写线程都是排他的 | 保证写线程以独占的方式修 改共享数据 |
读写锁允许 读读共享,读写互斥,写写互斥
在java.util.concurrent.locks包中定义了ReadWriteLock接口,该接口中定义了 readLock()返回读锁,定义 writeLock()方法返回写锁。该接口的实现类是 ReentrantReadWriteLock。
注意 readLock()与 writeLock()方法返回的锁对象是同一个锁的两个不同的角色,就像作者本人人在学校里是学生,下对象面前就是威猛先生(手动狗头),不是分别获得两个不同的锁。
锁 ReadWriteLock 接口实例可以充当两个角色。
读写锁的其他使用方法:
//定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获得读锁
Lock readLock = rwLock.readLock();
//获得写锁
Lock writeLock = rwLock.writeLock();
//读数据
try{
readLock.lock(); //申请读锁
读取共享数据
}finally{
readLock.unlock(); //总是在 finally 子句中释放锁
}
//写数据
try{
writeLock.lock(); //申请写锁
更新修改共享数据
}finally{
writeLock.unlock(); //总是在 finally 子句中释放锁
}
ReadWriteLock 读写锁可以实现多个线程同时读取共享数据,即读 读共享,可以提高程序的读取数据的效率。
例子:创建5个线程来获取读锁,线程获取之后,未释放锁,看看其它线程获取情况
public class TestOne {
//创建读写锁
static ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//创建读锁
static Lock readLock = readWriteLock.readLock();
public static void main(String[] args) {
TestOne t1=new TestOne();
//创建5个线程使用锁
for (int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
t1.read();
}
}).start();
}
}
//创建方法 供线程调用
public void read(){
try {
//申请读锁
readLock.lock();
System.out.println(Thread.currentThread().getName()+"获得读锁");
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程获得锁未释放锁
}
}
}
结果:
结论:在一个线程获取锁未释放的条件下,其他线程还可以继续获取锁,表明了,读锁在读线程之间是共享的,提高了数据的读取效率。
通过 ReadWriteLock 读写锁中的写锁,只允许有一个线程执行 lock() 后面的代码。
例子:创建两个线程来争夺写锁,看看一个线程获得锁后,不释放 看另一个锁的情况
public class TestOne {
//创建读写锁
static ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//创建写锁
static Lock writeLock =readWriteLock.writeLock();
public static void main(String[] args) {
TestOne t1=new TestOne();
//创建2个线程使用锁
for (int i=0;i<2;i++){
new Thread(new Runnable() {
@Override
public void run() {
t1.write();
}
}).start();
}
}
//创建方法 供线程调用
public void write(){
try {
//申请写锁
System.out.println(Thread.currentThread().getName()+"尝试获取写锁...");
writeLock.lock();
System.out.println(Thread.currentThread().getName()+"获得写锁成功,开始修改数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程获得写锁 不释放
}
}
}
结果:
结论:在一个线程获得写锁后,只要不释放锁,别的线程就要一直等待,这说明了写锁是互斥的,这也保证了数据共享的原子性
写锁是独占锁,是排他锁,所以读线程与写线程也是互斥的,有写锁被线程占有,那么别的锁就不可能被别的线程占有
例子:创建3 个线程 一个线程先获取写锁,两个线程尝试获取读锁 看情况
public class TestOne {
//创建读写锁
static ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//创建读锁
static Lock readLock=readWriteLock.readLock();
//创建写锁
static Lock writeLock =readWriteLock.writeLock();
public static void main(String[] args) throws InterruptedException {
TestOne t1=new TestOne();
//创建1个线程使用写锁,不释放
new Thread(new Runnable() {
@Override
public void run() {
t1.write();
}
}).start();
//main线程休眠1s确保 线程获得写锁
Thread.sleep(1000);
//创建2个线程尝试获得读锁
new Thread(new Runnable() {
@Override
public void run() {
t1.read();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
t1.read();
}
}).start();
}
//创建写锁方法 供线程调用
public void write(){
try {
//申读读锁
System.out.println(Thread.currentThread().getName()+"尝试获取写锁...");
writeLock.lock();
System.out.println(Thread.currentThread().getName()+"获得写锁成功,开始修改数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
//writeLock.unlock();
//线程获得读锁 不释放
}
}
//创建都锁方法
public void read(){
try {
//申请都锁
System.out.println(Thread.currentThread().getName()+"尝试获取读锁...");
readLock.lock();
System.out.println(Thread.currentThread().getName()+"获得读锁成功,开始读取数据");
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程获得写锁 不释放
}
}
}
结果:线程0获取写锁后未释放,其他线程都处于等待状态,无法获取读锁
如果获取写锁了之后 再释放了它,看其他线程的情况:
结果:写锁被释放后,其他两个线程可以获取读锁
结论:
可以看出在一个线程获取到写锁后,未释放写锁之前,其他线程也是无法获取读锁的,因为写锁是排他锁,就和synchronized () 和可重入锁一样的效果。也是未来共享数据的原子性。