前言
一、乐观锁和悲观锁
首先明确一点,乐观锁和悲观锁并不是一个实际意义上的锁,像是synchronized这种锁,而是一种抽象意义上的锁,是并发时的两种策略,可以意会一下。
乐观锁
顾名思义,非常乐观的锁,每次去拿数据以后都认为别人不会修改,所以不会上锁,但是如果想要更新数据,会在更新前检查在读取至更新这段时间别人有没有修改过这个数据(可以使用版本号机制和CAS算法实现)。如果修改过,则重新读取,再次尝试更新,循环上面的步骤直到更新成功。(除非线程放弃操作)
综上所述,乐观锁适用于多读的应用类型,这样可以提高吞吐量。类似的实现有数据库提供的write_condition机制和java.util.concurrent.atomic包下的原子变量类,就是使用了乐观锁的一种实现方法CAS实现的,稍微解释一下CAS,CAS主要有三个操作数,内存值V、旧的预期值A,要修改的新值B,它是如何来保证原子性的呢,当一个线程A修改了内存值V,此时另一个线程B通过CAS比较V与A的值,发现V与A的值不同,就操作失败了(因为如果还继续操作的话就会产生脏读,读到的是线程A修改的数据,所以操作失败)。
悲观锁
悲观锁就是非常的悲观,每次拿数据都认为别人会进行修改,所以每次拿数据的时候都会加锁,这样其他线程就只能阻塞知道这个锁释放。像是synchronized和ReentrantLock就是悲观锁的思想。
二、可重入锁
可重入锁其实就是递归锁,也就是可以重新进入的锁,也就是允许同一个线程获取同一把锁,比如一个递归函数中有加锁操作,那么会阻塞自己吗?答案是不会的,也就是这个锁是可重入的。比如JDK中提供的所有Lock实现类包括synchronized关键字的锁都是可重入锁。实际上大部分的业务用可重入锁就可以。
三、公平锁和非公平锁
公平锁就是有很多线程同时申请同一个锁,当锁释放的时候,先申请的先得到锁,非常公平。非公平锁的话,是按照随机的顺序或者其他优先级排序的,后申请的线程也有可能首先拿到锁。
synchronized是非公平锁,因为我们知道它在轻量级锁的情况下如果线程没有获得到锁,会进行自旋操作,这样对其他线程是不公平的。而ReentrantLock可以是非公平锁也可以是公平锁,默认是非公平锁。一般情况下,非公平锁的吞吐量比公平锁大,如果没有特殊要求,优先使用非公平锁。
四、可中断锁
可中断锁就是可以相应中断的锁。如果线程A持有锁,线程B等待获得该锁,由于线程A持有锁的时间太长了,线程B不想等待了,由线程A向线程B发出中断请求,也可以由B自己发出。这个中断请求不能直接终止线程,而是要被中断的线程自己做出决定。
synchronized是不可中断锁,而Lock的实现类都是可中断锁。
五、读写锁
读写锁是一对锁,一个读锁(共享锁)和一个写锁(互斥锁、排它锁)。
Java中的ReadWriteLock中的接口提供了一个读的方法一个写的方法,ReadWriteLock接口的唯一实现类是ReentrantReadWriteLock。
1 | public interface ReadWriteLock { |
在大部分的情况下,我们是知道对数据进行读取还是修改,那就可以在加锁的时候就规定读还是写。如果线程是为了读取,那么就加上读锁,其他线程就可以直接访问,不需要等待(读取器的数量要加1,只有当读取器的数量为0的时候,写锁才可以获取)。如果线程是为了写,那么就加上写的锁,其他线程要无论是要写还是要读都要等待,直到锁释放。
读写锁其实是悲观锁,因为不管是读锁还是写锁都加上了锁,而乐观锁的话是一种无锁的编程,比如CAS这种系统级别的原子操作。