根据锁的特性来进行分类
1、乐观锁和悲观锁
悲观锁: 对于一个数据的并发操作,悲观锁认为自己在使用数据的时候一定会有别的线程来修改数据,因此就要先加锁,确保数据不会被其他线程修改。synchronized关键字和Lock的实现类都是悲观锁。
乐观锁: 自己在使用数据的时候不会有其他线程来修改数据的,因此不会添加锁,只是在更新数据之前去判断这个数据是否被修改。如果已经被修改,那就根据不同的实现方式执行不同的操作(例如报错或者自动重试);如果没有被更新,那就将自己修改的数据成功写入。CAS算法就是使用无锁编程来实现的,是乐观锁。
根据上面的分析我们可以看出:
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确
乐观锁适合读操作多的场景,不加锁能使其读操作的性能大幅度提升
悲观锁很容易理解,咱们现在来看一下乐观锁——CAS算法
CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
- 需要读写的内存值 V
- 进行比较的值 A
- 要写入的新值 B
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作
乐观锁的三大问题:
- ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。JDK 1.5 以后的 AtomicStampedReference类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
- 循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。
- 只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。
2、自旋锁 VS自适应自旋锁
自旋锁的概念:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。
自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
3、无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁
无锁: 无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
偏向锁: 一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。
轻量级锁: 当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
重量级锁: 若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。
4、公平锁 VS 非公平锁
公平锁:
是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先到先得。
非公平锁:
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的情况下,有可能会造成优先级反转或者饥饿现象
5、可重入锁 VS 非可重入锁
ReentrantLock和synchronized都是重入锁
可重入锁:
有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够打到水。这就是可重入锁
非可重入锁
此时管理员只允许锁和同一个人的一个水桶绑定。第一个水桶和锁绑定打完水之后并不会释放锁,导致第二个水桶不能和锁绑定也无法打水。
6、独享锁 VS 共享锁
独享锁也叫排他锁,是指该锁一次只能被一个线程所持有
共享锁是指该锁可被多个线程所持有
参考自:https://blog.youkuaiyun.com/axiaoboge/article/details/84335452