9.1、悲观锁
当我们要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发的发生。为什么叫做悲观锁呢?因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。
9.2、乐观锁
乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁,只有到数据提交的时候才通过一种机制来验证数据是否存在冲突。乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。
9.3、表锁
我要操作某一条记录的时候,我害怕其它线程也来操作,我就把整个表锁起来。
9.4、行锁
我要操作某一条记录的时候,我害怕其它线程也来操作,我就把当前要操作的这一行记录锁起来。
9.5、读锁
共享锁,易发生死锁
9.6、写锁
独占锁,易发生死锁
9.7、总结
行锁,表锁,读锁,写锁,以及 syncronized 实现的锁均为悲观锁。
9.8、案例实现读写锁
不使用读写锁的情况
代码演示
package com.ae.juc.readWriteLock; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; //创建资源类 class Cache1{ //缓存 private volatile Map<String,Object> cache = new HashMap<>(); //写数据 public void put(String key, Object value){ System.out.println(Thread.currentThread().getName() + " 正在写操作 " + key); try { //暂停一会 TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } //写数据 cache.put(key,value); System.out.println(Thread.currentThread().getName() + " 写完了 " + key); } //读数据 public Object get(String key){ System.out.println(Thread.currentThread().getName() + " 正在读取操作 " + key); Object value = null; try { //暂停一会 TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } //读数据 value = cache.get(key); System.out.println(Thread.currentThread().getName() + " 读取完了 " + key); return value; } } /** * 问题:在没有写完的时候就来读了 */ public class ReadWriteLockDemo1 { public static void main(String[] args) { Cache1 cache = new Cache1(); //写数据 for (int i = 1; i <= 5 ; i++) { final int num = i; new Thread(() -> { cache.put(num+"", num+""); },String.valueOf(i)).start(); } //读数据 for (int i = 1; i <= 5 ; i++) { final int num = i; new Thread(() -> { cache.get(num+""); },String.valueOf(i)).start(); } } }
运行结果
1 正在写操作 1 3 正在写操作 3 4 正在写操作 4 5 正在写操作 5 2 正在写操作 2 1 正在读取操作 1 2 正在读取操作 2 4 正在读取操作 4 5 正在读取操作 5 3 正在读取操作 3 1 读取完了 1 4 写完了 4 5 读取完了 5 4 读取完了 4 2 读取完了 2 3 写完了 3 2 写完了 2 5 写完了 5 1 写完了 1 3 读取完了 3
使用读写锁的情况
代码演示
package com.ae.juc.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; //创建资源类 class Cache2{ //缓存 private volatile Map<String,Object> cache = new HashMap<>(); //创建读写锁 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //写数据 public void put(String key, Object value){ //增加写锁 readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + " 正在写操作 " + key); //暂停一会 TimeUnit.MICROSECONDS.sleep(300); //写数据 cache.put(key,value); System.out.println(Thread.currentThread().getName() + " 写完了 " + key); } catch (InterruptedException e) { e.printStackTrace(); } finally { //释放写锁 readWriteLock.writeLock().unlock(); } } //读数据 public Object get(String key){ //增加读锁 readWriteLock.readLock().lock(); Object value = null; try { System.out.println(Thread.currentThread().getName() + " 正在读取操作 " + key); //暂停一会 TimeUnit.MICROSECONDS.sleep(300); //读数据 value = cache.get(key); System.out.println(Thread.currentThread().getName() + " 读取完了 " + key); } catch (InterruptedException e) { e.printStackTrace(); } finally { //释放读锁 readWriteLock.readLock().unlock(); } return value; } } /** * 解决问题:使用读写锁来解决问题 */ public class ReadWriteLockDemo2 { public static void main(String[] args) { Cache2 cache = new Cache2(); //写数据 for (int i = 1; i <= 5 ; i++) { final int num = i; new Thread(() -> { cache.put(num+"", num+""); },String.valueOf(i)).start(); } //读数据 for (int i = 1; i <= 5 ; i++) { final int num = i; new Thread(() -> { cache.get(num+""); },String.valueOf(i)).start(); } } }
运行结果
2 正在写操作 2 2 写完了 2 1 正在写操作 1 1 写完了 1 3 正在写操作 3 3 写完了 3 5 正在写操作 5 5 写完了 5 4 正在写操作 4 4 写完了 4 4 正在读取操作 4 1 正在读取操作 1 2 正在读取操作 2 3 正在读取操作 3 5 正在读取操作 5 5 读取完了 5 3 读取完了 3 2 读取完了 2 4 读取完了 4 1 读取完了 1
9.9、读写锁
9.9.1、概念
9.9.2、读写锁解释
无锁的情况
多个线程同时去对某一个资源进行读写操作,会导致某些线程读取的数据不一致。这就会导致问题,所以引出了锁的概念。
加锁的情况:【synchronized、ReentrantLock】
这些锁都是独占的,在多线程情况下,不管你是读还是写,每一次只能有一个线程能拿到锁。那么问题来了,当很多线程都是同时来读,你没必要一次只能一个来读呀,效率极低,所以就引出读写锁。
读写锁的情况:【ReentrantReadWriteLock】
读写锁ReentrantReadWriteLock:读读共享,读写互斥,写写互斥_猿人小郑的博客-CSDN博客_读写锁读写互斥么
读写锁是将读锁和写锁分开,当很多线程同时来读,读读共享,线程同时读取,提高性能;当很多线程同时来写,写写互斥,只能一个线程一个线程的执行;当很多线程同时来读写,读写互斥,也只能一个线程一个线程的执行。所以,从整体的角度来看,读写锁ReentrantReadWriteLock在读多写少的情况下,还是能提升性能的。
9.10、读写锁的降级
ReentrantReadWriteLock 还允许通过获取写入锁定,然后读取锁定然后释放写入锁定从写入锁定到读取锁定。 但是,从读锁定升级到写锁是不可能的。简单说就是写锁可以降级为读锁,读锁不能生升级为写锁