并发编程——并发编程之锁分类

锁模型

​ 在前面JMM内存模型中有介绍过,其实原子性问题的源头是线程切换。如果我们把线程切换禁用了呢?单核CPU场景下好办,因为同一时刻单核就意味着只有一个线程执行,紧致CPU中断,那么操作系统就不会重新调度线程,也就禁止了线程切换。但是多核场景下,同一时刻可能有两个线程同时在执行。

​ 同一时刻只有一个线程在执行这个条件就是我们说的互斥,如果不管在单核CPU还是多核CPU,我们都保证对共享变量的修改是互斥的,那么也就可以保证原子性了。

​ 互斥的方案就是加锁,简单的锁模型如下:

在这里插入图片描述

临界区

在图中演示的需要被保护起来,互斥执行的代码就被称为临界区

​ 但上面的模型还有问题,这就是我们常说的,解铃还须系铃人,不然就好比我去上班了,走的时候把家里门锁上了,然后小红来了居然可以打开我家的门锁,这显然是不合理的。我家的门只有我能打开,这才是正确的做法,改进后的锁模型是这样的:
在这里插入图片描述

​ 我们先对临界区需要受保护的资源R,为它创建一把锁LR,然后针对这把锁在进出临界区的时候,加上加锁与解锁操作。


锁分类概念介绍

​ 锁从互斥与非互斥的角度可以分为乐观锁与悲观锁,从线程是否可以重新获取锁对象分为可重入锁与非可重入锁,从等候线程的排队角度,将锁分为公平锁与非公屏锁,从是否允许多个线程获取锁资源,分为共享锁与排他锁,从获取锁方式的角度分为阻塞锁与自旋锁。

下图个人总结,如有误请各位大佬留言指正

在这里插入图片描述


乐观锁与悲观锁

悲观锁:

​ 悲观锁,从名字上理解就是悲观的认为一定会有人跟你抢占资源,那么它拿到资源了,不管有没有人,都会把它锁起来,除了它自己,谁都别想获取到这个资源。

Java中的实现

​ (仅仅是举例嗷,不是说Java中就这么几个)

  • synchronized
  • ReentrantLock
乐观锁:

​ 乐观锁,则是十分乐观的认为没有人跟它抢这个资源,也就不会对它加锁。那么它怎么保证数据的安全性呢?实际上在它拿到资源时,会打上一个标记,在修改时检查一下这个标记,如果没被人改过,那么就意味着没有人来修改过这个资源,它就会提交修改,如果发现不一致被人修改过,那么反复尝试,直到成功为止。

Java中的实现
  • Unsafe
  • 原子类
数据库的实现

​ 在数据库中,也是存在并发问题的。多个线程同时修改同一个表的同一行数据,如果什么都不处理,很有可能会出现一个事务读取另一个事务修改、但是未提交的数据(脏读);又或者事务1在读取这行数据,事务2随后对它进行了修改,事务1再次读取就会发现两次读取结果不一致(不可重复读);又或者事务1对一行数据进行修改,同时事务2插入了一条新数据,恰巧部分数据跟事务1修改那行id一致,事务1回头再看就发现似乎刚刚没有修改(幻读)。为此,事务可以设置隔离级别,让用户根据需求选择并发情况下的具体数据不一致容忍方案。

MVCC,并发版本控制就是Mysql的Innodb引擎通过undolog对已提交读与可重复读这两种隔离级别的具体实现方式。

​ MVCC的实现机制如下:

  • innodb为每个表都增加两个隐藏的列:行创建时间与行过期时间,可以简单的理解为存储的就是系统自增生成的版本号。
  • 在多线程修改时,只会查找出<=当前行系统版本号的数据,这样就可以确保读取的数据要么是事务开始前就存在的,要么是事务自己插入或者修改的。

​ 通过MVCC,就避免了对行加锁,而且对读非阻塞,并且也解决了乐观锁的ABA问题。


可重入锁与非可重入锁

​ 可重入锁指的是一个线程可以重复获取锁,不会出现死锁;非可重入锁指的就是不允许一个线程重复获取锁

Java中的实现
可重入锁
  • synchronized

  • ReentrantLock

  • ReentrantReadWriteLock


公平锁与非公平锁

​ 公平锁和非公平锁其实指的是锁的等待队列的唤醒机制,如果在唤醒等待线程时,是按照排队顺序,先到先唤醒,那就是公平锁,如果允许插队,后面来的反而先排上队获取到锁了,那就是非公屏锁。

​ 为什么会有非公平锁呢,这其实是为了防止线程唤醒的时候CPU的资源浪费,而且非公平锁并不是无脑的插队,也是有策略的。比如读写锁:

  • ​ 写锁可以随时插队
  • ​ 读锁仅在等待队列头结点不是想获取写锁的线程时才可以插队

​ 还是挺好理解的啊,如果是无脑的插队,那么就会造成线程饥饿,可以认为非公平锁是在一定程度上公平的插队。

Java中的实现
  • ReentrantReadWriteLock:构造函数的入参fair即可指定公平/非公平。

共享锁与排他锁

​ 共享锁指的是允许多个线程同时访问临界区,排他锁就是我占用了临界区你就不能来。实际上排他锁与互斥锁没啥区别,共享锁与排他锁是在特殊场景:读写情况对互斥锁的进一步优化。在读的场景下,其实大家都是来读取,那么如果用了互斥锁就很浪费,因为大家都是读取其实就没必要加锁啊,在写的场景才需要加锁。

Java中的实现
  • ReentrantReadWriteLock
数据库的实现
  • Mysql的Innodb引擎的读写锁

自旋锁与阻塞锁

​ 自旋锁与阻塞锁是只在获取不到锁时,CPU处理策略的不同。自旋锁是我获取不到锁,那我就一直试图获取锁,CPU一直在这里忙碌;阻塞锁是我获取不到锁,那我就进入阻塞状态,让出CPU的使用权。其实自旋锁就是乐观锁的实现,阻塞锁就是悲观锁的实现。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值