- 悲观锁: 只有一个线程可以 *** 作资源, 也就是说这个资源只能线程一个一个来使用
- 乐观锁: 允许线程同时 *** 作资源, 但 *** 作完之后需要提交才能对资源进行改变, 必须和当前版本一致的 *** 作还能被提交, 而较旧的版本不能被提交
-
表锁: *** 作一张表的一行时, 会锁住整张表, 这时其他线程不能 *** 作这张表的任何数据
-
行锁: *** 作一张表的一行时, 只会锁住整张表的一行, 这时其他线程不能 *** 作这张表这一行, 但可以 *** 作其他行
一篇关于读写锁的易懂博客
读锁
: 不改变锁中数据的 *** 作,写锁
: 改变锁中数据的 *** 作
前面讲的
- 一个进程在读锁时, 允许其他线程也在读锁(但不能写), 读锁是共享的
- 一个进程在写锁时, 不允许其他线程进行任何 *** 作(包括读和写), 写锁时独占的
不同线程之间,读写互斥,同一个线程可以先获取写锁,再获取读锁,反过来不行
考虑一种情况:
如果两个线程同时对一个资源进行先读再写
的 *** 作, 那么:
- 线程1会等待线程2的读 *** 作结束后再写
- 线程2会等待线程1的读 *** 作结束后再写
这样就可能产生死锁
而读写锁就是, 使用一个类: ReentrantReadWriteLock
把读锁和写锁分开使用, 如果它的对象是lock
-
lock.writeLock().lock();
就是在使用写锁, 这时其他的线程无法 *** 作资源 -
而
lock.readLock().lock();
是在使用读锁, 这时其他的线程可以读去资源
举个栗子:
先写一个实体类,
ReadWriteMap
, 是一个既能读
又能写
的Map集合属性里面有两把锁: 一个
互斥锁
, 一个读写锁
, 但是只能使用其中的一把
/**
* 一个可读可写的map集合
*/
@Data
public class ReadWriteMap{
private final Map<Integer, String> map = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true); // 读写锁
private final ReentrantLock lock = new ReentrantLock(true); // 互斥锁
// 从map中读取数据
public void read(int i){
System.out.println(Thread.currentThread().getName() + "正在读数据...");
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
String s = map.get(i);
System.out.println("读取数据完成: {" + i + " ," + s + "}");
}
// // 往map中写入数据
public void write(int i){
System.out.println(Thread.currentThread().getName() + "正在写数据...");
try{
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
map.put(i, String.valueOf(i));
System.out.println("写入数据完成: {" + i + " ," + i + "}");
}
}
两个线程, 一个(AAA)往ReadWriteMap中
写数据
, 两个(BBB和CCC)从ReadWriteMap中读数据
- 第一种情况:
无锁
/**
* 1.没有任何锁的案列
*/
public class NoneLockDemo{
private static final ReadWriteMap map = new ReadWriteMap();
public static void main(String[] args){
new Thread(() -> {
for(int i = 0; i < 5; i ++)
map.write(i);
}, "AAA").start();
new Thread(() -> {
for(int i = 0; i < 5; i ++){
map.read(i);
}
}, "BBB").start();
}
}
结果: 还没写完就被读取, 结果是读取了错误的数据
- 第二种情况: 加上
互斥锁
/**
* 2.加上互斥锁的案例
*/
public class SyncLockDemo{
private static final ReadWriteMap map = new ReadWriteMap();
private static final ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args){
new Thread(() -> {
for(int i = 0; i < 5; i ++){
lock.lock();
try{
map.write(i);
} finally{
lock.unlock();
}
}
}, "AAA").start();
new Thread(() -> {
for(int i = 0; i < 5; i ++){
lock.lock();
try{
map.read(i);
} finally{
lock.unlock();
}
}
}, "BBB").start();
new Thread(() -> {
for(int i = 0; i < 5; i ++){
lock.lock();
try{
map.read(i);
} finally{
lock.unlock();
}
}
}, "CCC").start();
}
}
结果: 写完才能读取, 但两个读线程只能依次执行
- 第三种情况: 加上
读写锁
/**
* 3.读写锁的案例
*/
public class ReadWriteLockDemo{
private static final ReadWriteMap map = new ReadWriteMap();
private static final ReadWriteLock lock = map.getRwLock();
public static void main(String[] args){
new Thread(() -> {
for(int i = 0; i < 5; i ++){
lock.writeLock().lock();
try{
map.write(i);
}finally{
lock.writeLock().unlock();
}
}
}, "AAA").start();
new Thread(() -> {
for(int i = 0; i < 5; i ++){
lock.readLock().lock();
try{
map.read(i);
} finally{
lock.readLock().unlock();
}
}
}, "BBB").start();
new Thread(() -> {
for(int i = 0; i < 5; i ++){
lock.readLock().lock();
try{
map.read(i);
} finally{
lock.readLock().unlock();
}
}
}, "CCC").start();
}
}
结果: 写完才能读取, 但两个读线程可以并发地执行
锁降级写锁的权限是比读锁的权限要高的, 所以从一个线程从写锁变成读锁的过程叫降级
为什么要降级: 一个线程既想读又想写(正常情况下两者是互斥的), 那么需要先获取写锁
, 进行写 *** 作, 写完之后, 再获取读锁
(锁降级), 进行读 *** 作(这时不能写了); 然后依次释放写锁和读锁
反之, 读锁不能升级为写锁, 也就是说不能先获取读锁,再获取写锁
/**
* 锁降级的案例
*/
public class LockDownDemo{
private static final ReadWriteMap map = new ReadWriteMap();
private static final ReadWriteLock lock = map.getRwLock();
public static void main(String[] args){
new Thread(() -> {
lock.writeLock().lock();
map.write(1);
lock.readLock().lock();
try{
map.read(1);
map.write(2);
}finally{
lock.writeLock().unlock();
lock.readLock().unlock();
}
}, "AAA").start();
}
}
使用了上面的类:
ReadWriteMap
注意: 这是一个线程, 所以他自己不管在使用哪个锁, 他都可以
读和写
, 因为读锁和写锁限制的是其他线程
的 *** 作
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)