一、乐观锁 & 悲观锁
乐观锁:指的是在操作数据的时候非常乐观,认为别人不会同时修改数据,因此乐观锁默认是不会上锁的,只有在执行更新的时候才会去判断在此期间别人是否修改了数据,如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:指的是在操作数据的时候比较悲观,认为别人一定会同时修改数据,因此悲观锁在操作数据时是直接把数据上锁,直到操作完成之后才会释放锁,在上锁期间其他人不能操作数据。
乐观锁和悲观锁各有优劣,在读操作多的场景适用乐观锁,因为数据不会被其他线程频繁修改,少了加锁和释放锁的开销性能更好;而在写操作多的场景下乐观锁就不适用了,数据会被其他线程修改就导致频繁更新失败。在 Java 中,CAS 机制属于乐观锁,synchronized 关键字的实现属于悲观锁。
二、独占锁 & 共享锁
共享锁:也叫读锁,可以查看数据,但是不能修改和删除的一种数据锁,加锁后其他的用户可以并发读取,但不能修改、增加、删除数据,该锁可被多个线程持有,用于资源数据共享。
独占锁:也叫写锁、排它锁,该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁都会被阻塞,直到当前线程解锁。
三、公平锁 & 非公平锁
公平锁:有多个线程按照申请锁的顺序来获取锁,就是说在一个线程组里面,能够保证每个线程都能拿到锁。
非公平锁:获取锁的方式是随机的,保证不了每个线程都能拿到锁,会存在有的线程饿死,一直拿不到锁。
非公平锁性能高于公平锁,因为非公平锁的竞争更消耗 CPU 资源。在 Java 中,synchronized 关键字是非公平锁,ReentrantLock 通过参数控制,可以是公平锁也可以是非公平锁,默认使用非公平锁。
四、可重入锁 & 不可重入锁
可重入锁:可以重复使用的锁,当一个线程获取锁之后,这个线程可以再次获取本对象实例上的锁,而其他的线程是不可以的。
不可重入锁:不可以重复使用的锁。
举个例子:假设有个类的A、B连个方法都加了锁,可重入锁在获取了A方法的锁之后可以直接访问B方法,而不可重入锁获取了A方法的锁之后访问B方法,需要等A方法的锁释放之后再次获取锁才能访问。Java 中的 synchronized 和 ReentrantLock 都是可重入锁。
五、互斥锁 & 自旋锁
互斥锁:锁只能被一个线程持有,当一个线程获取锁之后另外一个线程加锁就会失败,而加锁失败的线程会释放掉 CPU 资源。
自旋锁:和互斥锁有点类似,区别在于加锁失败后不会释放 CPU 资源,而是通过循环的方式尝试去获取锁。
六、偏向锁 & 轻量级锁 & 重量级锁
偏向锁:它会偏向于第一个访问锁的线程,如果在运行过程中,锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。
轻量级锁:如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM 会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
重量级锁:如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这是有一种情况就是其它线程已经为这个对象加上了轻量级锁,这是就要进行锁膨胀,将轻量级锁变成重量级锁。
七、分段锁
分段锁其实就是对锁的粒度的一种细化,以此来提高性能和灵活度。比如 Java7 中的 ConcurrentHashMap 就是采用的分段锁。