悲观锁
对任何数据类的操作持悲观的态度,认为任何数据操作都会发生线程不安全的现象。所以对每一个操作都会加上悲观锁。悲观锁的实现往往依靠数据库提供的锁机制。
乐观锁
任何数据操作在一般情况下是不会产生线程不安全的问题的,持乐观的态度。其不会在数据操作前加锁,这样也就不会发生等待。乐观锁会在数据进行提交更新时再上锁,再开始对数据进行检测。通过一种类似CAS的感觉,比较数据前后版本号version(自加字段)或者业务的状态来判断操作是否有效。
公平锁和非公平锁
根据锁的抢占机制,可以将锁给分为公平锁和非公平锁。公平锁指的是先到先得,非公平锁指的是谁抢到归谁。
ReentrantLock提供了公平和非公平锁的实现。
- 公平锁:ReentrantLock lock = new ReentrantLock(true)
- 非公平锁(默认):ReentrantLock lock = new ReentrantLock(false)
一般尽量使用非公平的锁。
独占锁和共享锁
根据锁能被单个线程占有还是多个线程占有来进行划分。
独占锁在任何时候保证只有一个线程获取到锁,ReentrantLock就是以独占的方式实现。多数写的操作用独占锁可以保证线程安全
共享锁则可以由多个线程同时持有,比如ReadWriteLock读写锁,它允许一个资源被多个线程执行读操作。读操作一般允许用共享锁,读并不会影响数据的一致性。
独占锁是一种悲观锁。
共享锁是一种乐观锁。
可重入锁
当一个线程想要获取其它线程的锁时,会被阻塞,但如果该线程想要重新获取自己已有的锁,如果不被阻塞,则称自己的这把锁为可重入锁。
synchronized内置锁其实就是一把可重入锁。
可重入锁的原理,其实就是在锁内维护一个线程标示,从来标示该锁目前被哪个线程占用,然后关联一个计数器,一开始计数器值为0,说明该锁没有被任何线程占用,当一个线程占用该锁后,计数器的值就会变为1,如果其他线程再来获取该锁,就会发现该锁已经有拥有者了,阻塞而挂起。
但当获取该锁的线程发现再去获取的锁的线程是自己,则再对计数器+1,然后释放锁-1,等到计数器为0时,锁的线程标示为null,则会唤醒之前处于挂起状态的线程重新来竞争锁。
自旋锁
因为一个线程获取不到锁而被阻塞后,要被挂起。而这个挂起的过程,就是从用户状态切换到内核状态,而获取到锁后,又要重新从内核状态切换到用户状态,这样的切换是十分浪费时间的。而自旋锁,则是让该线程在被阻塞后,并不马上进入内核状态,而是通过一定次数的尝试获取锁,如果在这个次数内能获取到锁,则可以直接执行,如果获取不到锁,那么还是只能老老实实切换到内核状态。
这个尝试的次数可以使用 -XX:PreBlockSpinsh参数设置该值。
所以自旋锁是用CPU的时间来换取线程阻塞和调度的开销,但这些时间也可能被浪费掉。