复习悲观锁和乐观锁

悲观锁进行数据操作时只能有一个线程,只要这个线程一执行就会给账户上锁,等到此线程完毕才会解锁,其他线程才可以对此账户继续操作
悲观锁可以解决并发中的问题,但是恰恰因为其不支持并发操作,所以使用悲观锁后代码效率低
image.png
乐观锁会有一个版本号属性,当一个线程对数据对数据进行操作之后会更新版本号version。其他线程也可以对数据进行操作,但是在提交之前会比较版本号,版本号低于version则提交失败
image.png

复习表锁和行锁

image.png

  1. 表锁:只操作一条记录,但是整张表都上锁,整个表都锁住
  2. 行锁:只操作一条记录,就只对这条记录所在行进行上锁,其他线程仍可操作其他行数据
  3. 行锁会发生死锁而表锁不会

    读写锁

    关于死锁

    image.png
    读锁发生死锁解释:线程1,2都要对数据进行读取和修改,但是线程1必须等待线程2读取完才可以对数据进行修改,不然会发生脏读。所以线程1 就要等待2读完。同理线程2也要修改,也要等待线程1读完,这样两个线程都不会修改数据,发生死锁。
    image.png
    写锁发生死锁解释:线程1先对第一条记录操作接着要对第二条记录操作;线程2先对第二条记录操作,在对第一条记录操作;这样就出现了线程1必须等线程2执行完毕才会对第二条记录操作,但线程2要执行完毕必须要修改第一条记录,但此记录正在线程1的控制中,所以就出现了相互等待;这样的相互等待出现了死锁

    读写锁概述

    现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那 么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以 应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源, 就不应该允许其他线程对该资源进行读和写的操作了。 简而言之:可以共同读,只可以单一写
    针对这种场景,JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称 为排他锁

  4. 线程进入读锁的前提条件:

    1. 没有其他线程的写锁
    2. 没有写请求, 或者==有写请求,但调用线程和持有锁的线程是同一个(可重入 锁)。==
  5. 线程进入写锁的前提条件:

    1. 没有其他线程的读锁
    2. 没有其他线程的写锁

      读写锁案例

      模拟多线程在map中取数据和读数据 ```java //资源类 class MyCache { //创建map集合,volatile表示数据不断发生变化 private volatile Map map = new HashMap<>();

      //创建读写锁对象 private ReadWriteLock rwLock = new ReentrantReadWriteLock();

      //写数据 public void put(String key,Object value) { //添加写锁 rwLock.writeLock().lock();

      try {

      1. System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
      2. //暂停一会
      3. TimeUnit.MICROSECONDS.sleep(300);
      4. //放数据
      5. map.put(key,value);
      6. System.out.println(Thread.currentThread().getName()+" 写完了"+key);

      } catch (InterruptedException e) {

       e.printStackTrace();
      

      } finally {

       //释放写锁
       rwLock.writeLock().unlock();
      

      } }

      //读数据 public Object get(String key) { //添加读锁 rwLock.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 {

       //释放读锁
       rwLock.readLock().unlock();
      

      } return result; } }

public class ReadWriteLockDemo { public static void main(String[] args) throws InterruptedException { MyCache myCache = new MyCache(); //创建线程放数据 for (int i = 1; i <=5; i++) { final int num = i; new Thread(()->{ myCache.put(num+””,num+””); },String.valueOf(i)).start(); }

    TimeUnit.MICROSECONDS.sleep(300);

    //创建线程取数据
    for (int i = 1; i <=5; i++) {
        final int num = i;
        new Thread(()->{
            myCache.get(num+"");
        },String.valueOf(i)).start();
    }
}

}

========================================================= 1 正在写操作1 1 写完了1 2 正在写操作2 2 写完了2 4 正在写操作4 4 写完了4 3 正在写操作3 3 写完了3 5 正在写操作5 5 写完了5 1 正在读取操作1 3 正在读取操作3 5 正在读取操作5 2 正在读取操作2 4 正在读取操作4 3 取完了3 5 取完了5 2 取完了2 1 取完了1 4 取完了4

Process finished with exit code 0

可见读可以一起读,写只能单独写
<a name="zVmL5"></a>
## 读写锁深入
<a name="pabwB"></a>
### 演变和优缺点
![14-读写锁演变.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1632126782906-52916ed0-d9a6-428c-8524-3c6c7bf311cc.png#clientId=u06a3aa1f-0921-4&from=paste&height=662&id=u06cb6320&margin=%5Bobject%20Object%5D&name=14-%E8%AF%BB%E5%86%99%E9%94%81%E6%BC%94%E5%8F%98.png&originHeight=662&originWidth=1350&originalType=binary&ratio=1&size=23847&status=done&style=none&taskId=uad75fcb9-9e72-47cd-b90d-17f30301997&width=1350)

<a name="Cg554"></a>
### 锁降级
![15-读写锁降级.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1632126845876-375e9e0f-25a9-4e0f-a761-c15198ddfef3.png#clientId=u06a3aa1f-0921-4&from=paste&height=662&id=ubf9cfb00&margin=%5Bobject%20Object%5D&name=15-%E8%AF%BB%E5%86%99%E9%94%81%E9%99%8D%E7%BA%A7.png&originHeight=662&originWidth=1350&originalType=binary&ratio=1&size=14984&status=done&style=none&taskId=u768e1412-9d91-4065-aa1a-52dbb6608eb&width=1350)
```java
//演示读写锁降级
public class ReentrantReadWriteLockDemo{

    public static void main(String[] args) {
        //可重入读写锁对象
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁

        //锁降级
        //1 获取写锁
        writeLock.lock();
        System.out.println("---write");

        //2 获取读锁
        readLock.lock();
        System.out.println("---read");        //输出read说明获取到了读锁

        //3 释放写锁
        writeLock.unlock();

        //4 释放读锁
        readLock.unlock();
    }
}

//若1,2互换位置,则获取不到写锁,因为还没读完没释放读锁。说明读锁不能升级为写锁

//读的时候不能写,写的时候可以读(锁降级)

原因
当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把 获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写 锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释 放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。