目录
读写锁:
锁降级
锁饥饿:
读写锁:
定义:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。
特点:读写互斥,写锁独占,读读可共享,读没有完成的时候其它线程写锁无法获得
public class ReentrantReadWriteLockDemo1 {
public static void main(String[] args) {
MyResource myResource = new MyResource();
for (int i = 0; i < 10; i++) {
String finalI1 = String.valueOf(i);
new Thread(() -> {
myResource.write(finalI1, finalI1);
}, String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
String finalI = String.valueOf(i);
new Thread(() -> {
myResource.read(finalI);
}, String.valueOf(i)).start();
}
try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}
for (int i = 0; i < 3; i++) {
String finalI = String.valueOf(i);
new Thread(()->{
myResource.write("新的写锁"+finalI,finalI);
},String.valueOf(i)).start();
}
}
}
class MyResource {
Map<String, Object> map = new HashMap<>();
Lock lock = new ReentrantLock();
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void write(String key, String value) {
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "写锁正在写入");
map.put(key, value);
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" +"写入完成");
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
public void read(String key) {
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t" + "读锁正在读取");
Object o = map.get(key);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" +"读取完成"+"\t"+String.valueOf(o));
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
}
必须等到读锁读完资源,新的写锁才能获取,继续写入
锁降级
如果同一个线程持有了写锁,在没有释放写锁的情况下,它还可以继续获取读锁。这就是写锁的降级,降级成为了读锁。
写锁降级的次序策略:
遵循获得写锁---》再获取读锁---》再释放写锁的次序,写锁能降级成为读锁。
相当于同一个线程自己持有写锁时再去拿读锁,其本质是重入锁。
示例:
public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
writeLock.lock();
System.out.println("先写入");
readLock.lock();
System.out.println("后读取");
writeLock.unlock();
readLock.unlock();
}
遵循锁降级次序策略:先获取写锁-再获取读锁--在释放写锁,没有阻塞正常释放锁结束。
反之:
/**
* 锁降级
*/
public class LockDownGradingDemo2 {
public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
readLock.lock();
System.out.println("先读取");
writeLock.lock();
System.out.println("后写入");
writeLock.unlock();
readLock.unlock();
}
}
先获取读锁,则一直阻塞,不能获取写锁。必须等到读锁释放锁后,获取写锁。可以理解为看了一半复联4,突然电影被改成上海堡垒,显然是不可接受的。
锁饥饿:
因为读的线程特别多,而且读写锁中的读锁是一种悲观锁,没有读完是不会释放的,所以会造成写锁长时间获取不到锁,出现锁饥饿的现象。
假设有1000个线程,其中读锁999个,写锁1个,每个读锁要读取10s,这样会造成写锁一直抢不到锁的情况,出现锁饥饿的问题。