1. 悲观锁:

共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程.Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。一般多写的场景下用悲观锁就比较合适

2. 乐观锁:

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据. 可以使用版本号机制和CAS算法实现。乐观锁适用于写比较少的情况下

2.1 CAS原理(Compare-and-Swap):

2.1.1 核心原理:

假设两个线程t1和t2, 都企图去修改主内存中的value值(V)将其改成N, 在修改的时候需要传入一个期望值E(你认为此刻主内存中的值多少). 如果期望值和主内存中的值一致, 那么允许修改,且更新主内存value值。如果不符合条件, 继续比较, 直至成功。这个就类似于SVN或者Git的版本号,如果没有人更改过,就能够正常提交,否者需要先将代码pull下来,合并代码后,然后提交。
核心代码如下:

  1. public final void cas(long E, int N) {
  2. int V;
  3. while(!this.compareAndSwapInt(E, V, N)) {
  4. ...
  5. }
  6. }

image.png

2.1.3 CAS机制的缺点:

1.ABA问题:
CAS在操作的时候会检查变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B,最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。解决方案: 在每次进行操作的时候加上一个版本号,每次改变时加1,即A -> B -> A,变成1A -> 2B -> 3A
2.消耗CPU:
使用CAS,每次都有很多的线程在竞争,也就是说CAS机制不能更新成功。这种情况下CAS机制会一直重试,这样就会比较耗费CPU。最差的情况,就是某个线程一直取到的值和预期值都不一样,这样就会无限循环.
3.只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以通过循环CAS的方式来保证原子操作
但是对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候只能用锁来保证原子性

2.1.4 CAS使用: (原子类)

https://www.yuque.com/wangyanyang/qz7fwa/shodal

2.2 版本控制:

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

  1. 步骤一: select * from OptimisticLock where id = 2 -- 查询得到版本是 1
  2. 步骤二: update OptimisticLock
  3. set `status` = 'finish',version = version + 1
  4. where id = 2 and version = 1

3. Synchronized锁

请参考: https://www.yuque.com/wangyanyang/qz7fwa/naiggo

4. Lock锁:

请参考: https://www.yuque.com/wangyanyang/qz7fwa/gtr8g2