[Java并发编程-8] 读写锁

悲观锁和乐观锁

  • 悲观锁: 只有一个线程可以操作资源, 也就是说这个资源只能线程一个一个来使用

img

  • 乐观锁: 允许线程同时操作资源, 但操作完之后需要提交才能对资源进行改变, 必须和当前版本一致的操作还能被提交, 而较旧的版本不能被提交

img

表锁和行锁

  • 表锁: 操作一张表的一行时, 会锁住整张表, 这时其他线程不能操作这张表的任何数据

  • 行锁: 操作一张表的一行时, 只会锁住整张表的一行, 这时其他线程不能操作这张表这一行, 但可以操作其他行

img

读写锁

一篇关于读写锁的易懂博客

读锁: 不改变锁中数据的操作,写锁: 改变锁中数据的操作

前面讲的

  • 一个进程在读锁时, 允许其他线程也在读锁(但不能写), 读锁是共享的
  • 一个进程在写锁时, 不允许其他线程进行任何操作(包括读和写), 写锁时独占的

不同线程之间,读写互斥,同一个线程可以先获取写锁,再获取读锁,反过来不行

考虑一种情况:

如果两个线程同时对一个资源进行先读再写的操作, 那么:

  • 线程1会等待线程2的读操作结束后再写
  • 线程2会等待线程1的读操作结束后再写

这样就可能产生死锁

读写锁就是, 使用一个类: ReentrantReadWriteLock 把读锁和写锁分开使用, 如果它的对象是lock

  • lock.writeLock().lock();就是在使用写锁, 这时其他的线程无法操作资源

  • lock.readLock().lock();是在使用读锁, 这时其他的线程可以读去资源

读写锁的演变

img

三种经典情况

举个栗子:

先写一个实体类, 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. 第一种情况: 无锁
/**
 * 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();
    }
}

结果: 还没写完就被读取, 结果是读取了错误的数据

  1. 第二种情况: 加上互斥锁
/**
 * 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();
    }
}

结果: 写完才能读取, 但两个读线程只能依次执行

  1. 第三种情况: 加上读写锁
/**
 * 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();
    }
}

结果: 写完才能读取, 但两个读线程可以并发地执行

锁降级

写锁的权限是比读锁的权限要高的, 所以从一个线程从写锁变成读锁的过程叫降级

为什么要降级: 一个线程既想读又想写(正常情况下两者是互斥的), 那么需要先获取写锁, 进行写操作, 写完之后, 再获取读锁(锁降级), 进行读操作(这时不能写了); 然后依次释放写锁和读锁

反之, 读锁不能升级为写锁, 也就是说不能先获取读锁,再获取写锁

img

/**
 * 锁降级的案例
 */
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

注意: 这是一个线程, 所以他自己不管在使用哪个锁, 他都可以读和写, 因为读锁和写锁限制的是其他线程的操作

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值