1.悲观锁和乐观锁:
悲观锁和乐观锁是我们常见的锁策略之一。我们来看看两者的特点。
乐观锁:我们认为在发生锁冲突比较不严重的情况下,甚至不会发生锁冲突,我们采用乐观锁策略。乐观锁的行为就是直接对数据进行访问,当我们进行数据提交的时候再检查是否发生冲突。
悲观锁:我们认为在发生锁冲突比较严重的情况下,我们采用悲观锁策略。每次获得数据我们都会对其进行加锁操作,保证其他人不会对数据进行修改。
我们描述一个场景,可以更好的理解悲观锁和乐观锁:
我们在学习过程中总会产生一些疑惑,这时候我们就会去询问老师,乐观锁策略就是这个同学认为老师比较闲,大概率会对我问题进行解答,然后这个同学直接把问题发给老师,也没有问老师有没有空,如果老师刚好有空,就会对其进行解答,如果老师这时候确实比较忙没空回答,这时候这位同学就下次再来问老师。
悲观锁的策略则是,这位同学认为老师平时都很忙,然后这位同学会首先发个消息给老师问老师是否有空,如果老师回了有空,这位同学才会接下来把问题提出。如果老师回了没空,这位同学就会阻塞等待一段时间,等下次再来问老师。
既然我们了解了悲观锁和乐观锁,那我们乐观锁是怎么实现的呢?
我们实现乐观锁有多种方式,我们这里介绍比较简单的实现方案,那就是加版本号。
首先我们两个线程的工作内存会读取主内存的数据count和版本号version到自己的工作内存中
然后线程1进行对数据进行减少50操作,线程2把数据-减少20
然后我们线程1先把数据写回到主内存中这时候主内存的版本号就会+1。
当我们的线程2准备把工作内存写入到主内存中的时候发现,主内存和版本号和工作内存的版本号不一样,这时候就会写回失败。如果再想进行修改操作,线程2会读取主存中新的数据和版本号在进行操作。我们想要成功写回数据,那就要保证主存的版本号和线程中工作内存的版本号一致。
2.读写锁:
读写锁理解起来就比较简单,读写锁策略大概分为三种情况。
假设我们线程有两个线程在工作:
1.当两个线程都在进行读操作的时候,我们不进行加锁处理,因为我们并没有读数据进行修改,所以不会导致线程不安全。
2.当两个线程一个在读,一个在写,这时候就会出现线程安全问题,这时候我们对写加锁。
3.当两个线程都在写的时候,这时候也会有线程安全问题,这时候我们对写进行加锁。
所以我们读写锁策略一般用于读多写少的场景下进行使用。
3.重量级锁和轻量级锁:
这组锁策略也是比较好理解的,我们可以这么理解如果开销比较大的就是重量级锁,开销小的为轻量级锁。
什么是开销大呢,那就是频繁的在用户态和内核态进行切换。什么是用户态和内核态呢。我们可以这么理解,当我们去银行办理业务的时候,柜台工作人员要我们提供身份证复印件,你直接把这个工作交给工作人员去完成,工作人员非常的忙,好不容易才拿到复印件,然后你再去排队办理业务,柜台又说,出示一下xxx,你又没有,然后你再给工作人员进行处理,反复和工作人员沟通, 还需要重新排队,这时候效率很低。恰好银行有个复印机,你就可以自己进行复印操作,然后等全部东西都准备好了,再去办理业务,那效率就变得高了不少。
重量级锁就是频繁进行用户态和内核态得切换。很容易引发线程的调度。开销较大。
轻量级锁就是少量的内核态用户态切换. 不太容易引发线程调度。开销较小。
4.自旋锁和挂起等待锁:
当我们并发编程的时候,当一个线程获取到了锁,那另一个线程想要获取到锁,这个线程不是进行阻塞等待,而是开始进行高速循环,没过一个时间就尝试获得这个锁,这就是自旋锁策略。那我们自选锁有什么优点呢,我们自旋的这个线程每次都尝试去获取锁而不是去等待,那这个线程就可以第一时间获取到锁资源。但是也有缺点,那就是我们在高速循环获取锁的过程中,会导致cpu空转,因为我们这个线程一直在工作但是啥也没干,就会对cpu的资源进行消耗。
挂起等待锁,在一个线程获取到锁资源的时候,其他线程想要获得锁,但是这个锁被占有,这时候其他线程就会进入阻塞等待,等到锁资源被释放,然后其他线程对锁进行竞争。挂起等待锁的优点就是,线程进入了阻塞等待,cpu就可以暂时可以不去调度他们,等到锁资源释放的时候,再进行调度,减少了cpu的使用。缺点就是线程无法第一时间获取到锁,需要对锁进行竞争。
方便理解我们想象这么一个场景:当我们去追求异性的时候,这位异性刚好有了伴侣,自旋锁策略就是每隔一段时间就对这位异性进行询问,你现在是单身吗?如果是就可以第一时间进行追求。但是我们的挂起等待锁,就会直接去自闭,然后过了好长一段时间再来问,这时候这位异性其实已经换了好几个伴侣了。
5.公平锁和非公平锁:
这组锁策略,非常好理解,我们可以想象这么一个场景:
我们日常生活中肯定遇到过排队,每个人都非常遵守规则,井井有条。每个人都按顺序去执行。
公平锁就是这样子,每个线程会进去一个队列中,进行排队操作,先来后到分的很清楚,然后当一个锁被释放的时候,我们由队头来获取锁,按顺序进行获取资源。
非公平锁就是大家一起竞争,每个人竞争到锁的概率是一样的,不像公平锁一样排队去获取。没竞争到的线程就进入等待,等到锁资源再次被释放,就再次进行竞争。
6.可重入锁和不可重入锁:
这组锁策略也好理解。可重入锁就是我们对同一个对象进行两次加锁的时候我们不会出现死锁的情况。
// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待.
lock();
当是不可重复锁策略的时候,这是我们就会出现死锁的情况,第二次加锁在等第一次加锁释放锁,第一次加锁想要释放锁要执行完指定代码块,然后就卡在了这个形成了死锁。而可重入锁就不会出现这样的问题。