33.Java读写锁(认识读写锁、读写锁案例、锁降级、锁的演变)

一、锁概述

1、悲观锁
  • 每个人进行操作时都进行上锁解锁,能解决并发问题,但不支持并发操作,只能逐个进行操作,效率低
2、乐观锁
  • 通过版本号进行控制,谁先提交就先修改版本号,其他人因为版本号不相同就不能进行提交
3、表锁
  • 对整个表加锁,不会发生死锁
4、行锁
  • 对表中的单独一行加锁,会发生死锁
5、读锁
  • 共享锁,可以有多个人读,会发生死锁
6、写锁
  • 独占锁,只能有一个人写,会发生死锁

二、读写锁

1、基本介绍
  • 对共享资源有读和写的操作,且写操作没有读操作那么频繁时,在没有写操作的时候,应该允许多个线程同时读取共享资源,但是当一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作

  • 针对这种场景,Java 提供了读写锁 ReentrantReadWriteLock,它表示两个锁,一个是与读操作相关的锁,称为共享锁,另一个是写相关的锁,称为排他锁

2、获取读写锁的前提条件
(1)线程获取读锁的前提条件
  • 没有其他线程的写锁

  • 没有写操作,或者写操作在读操作之前,且调用线程和持有锁的线程是同一个(可重入锁)

(2)线程获取写锁的前提条件
  • 没有其他线程的读锁和写锁

  • 没有读操作

3、读写锁操作
  • 创建读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
  • 获取读锁
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
  • 获取写锁
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
  • 读锁的上锁与解锁
readLock.lock();
readLock.unlock();
  • 写锁的上锁与解锁
writeLock.lock();
writeLock.unlock();

三、读写锁案例

1、需求
  • 使用多线程对 Map 集合进行读写操作
2、不使用锁的问题
package com.my.readwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 读操作
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(num + "", num + "");
            }, "" + i).start();
        }

        // 写操作
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num + "");
            }, "" + i).start();
        }
    }
}

class MyCache {
    // 创建 Map 集合
    private volatile Map<String, Object> map = new HashMap<>();

    // 写数据
    public void put(String key, Object value) {
        try {
            System.out.println(Thread.currentThread().getName() + " 正在进行写 " + key);
            TimeUnit.MICROSECONDS.sleep(300);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 写完了 " + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 读操作
    public Object get(String key) {
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName() + " 正在进行读 " + key);
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读完了 " + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return result;
    }
}
  • 结果
1 正在进行写 1
5 正在进行写 5
4 正在进行写 4
2 正在进行写 2
3 正在进行写 3
1 正在进行读 1
2 正在进行读 2
3 正在进行读 3
4 正在进行读 4
5 正在进行读 5
4 读完了 4
5 读完了 5
4 写完了 4
3 读完了 3
2 写完了 2
3 写完了 3
1 写完了 1
1 读完了 1
2 读完了 2
5 写完了 5
3、使用读写锁实现
package com.my.readwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 读操作
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(num + "", num + "");
            }, "" + i).start();
        }

        // 写操作
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num + "");
            }, "" + i).start();
        }
    }
}

class MyCache {
    // 创建 Map 集合
    private volatile Map<String, Object> map = new HashMap<>();

    // 创建读写锁
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    // 写数据
    public void put(String key, Object value) {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " 正在进行写 " + key);
            TimeUnit.MICROSECONDS.sleep(300);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 写完了 " + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    // 读操作
    public Object get(String key) {
        lock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName() + " 正在进行读 " + key);
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读完了 " + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
        return result;
    }
}
  • 结果
2 正在进行写 2
2 写完了 2
3 正在进行写 3
3 写完了 3
1 正在进行写 1
1 写完了 1
4 正在进行写 4
4 写完了 4
5 正在进行写 5
5 写完了 5
1 正在进行读 1
2 正在进行读 2
4 正在进行读 4
3 正在进行读 3
5 正在进行读 5
5 读完了 5
2 读完了 2
1 读完了 1
4 读完了 4
3 读完了 3

四、锁降级

1、基本介绍
  • 在线程持有写锁时,该线程可以继续获取读锁

  • 在线程持有读锁时,该线程不能继续获取写锁

  • 当线程获取写锁时,它一定独占了读写锁,因此可以继续让获取读锁,当同时获取了写锁和读锁后,还可以先释放写锁并继续持有读锁,这样写锁就能降级为读锁

  • 当线程获取读锁时,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程升级为写锁

2、降级步骤
  • 获取写锁 -> 获取读锁 -> 释放写锁 -> 释放读锁
3、具体实现
package com.my.readwritelock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDowngradeTest {
    public static void main(String[] args) {
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
        ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();

        // 获取写锁
        writeLock.lock();
        System.out.println("Hello1");

        // 获取读锁
        readLock.lock();
        System.out.println("Hello3");

        // 释放写锁
        writeLock.lock();
        System.out.println("Hello3");

        // 释放读锁
        readLock.unlock();
    }
}
  • 结果
Hello1
Hello3
Hello3
4、错误演示
  • 读锁不能升级为写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();

// 获取读锁
readLock.lock();
System.out.println("Hello1");

// 获取写锁
writeLock.lock();
System.out.println("Hello2");

// 释放读锁
readLock.unlock();
System.out.println("Hello3");

// 释放写锁
writeLock.unlock();
  • 结果
Hello1

五、锁的演变总结

1、无锁
  • 会出现多线程抢夺资源
2、synchronized 和 ReentrantLock
  • 这两种锁都是独占的,每次只能进行一个操作,不能共享
3、ReentrantReadWriteLock
  • 可以多人读,提升性能,但不能多人写

  • 因为写锁是独占的,也就意味着进行写的的时候不能有读锁在占着,读锁是共享的,假如读并发很多的时候,写锁就会一直进不去,可能会造成锁饥饿

  • 读的时候,不能写,只有读完了,才可以写

  • 读进程不能写,写进程可以读

4、锁降级
  • 一般等级写锁的高于读锁,可将写锁降级为读锁,但是不可以读锁升级为写锁

你可能感兴趣的:(Java,-,基础入门,java,开发语言,java-ee,intellij-idea,intellij,idea,spring,boot,后端)