1、乐观锁和悲观锁
主要用于数据库数据的操作中,而对于线程锁中较为少见。
悲观锁和乐观锁是一种加锁思想。对于乐观锁,在进行数据读取的时候不会加锁,而在进行写入操作的时候会判断一下数据是否被其它线程修改过,如果修改则更新数据,如果没有则继续进行数据写入操作。乐观锁不是系统中自带的锁,而是一种数据读取写入思想。应用场景例如:在向数据库中插入数据的时候,先从数据库中读取记录修改版本标识字段,如果该字段没有发生变化(没有其他线程对数据进行写操作)则执行写入操作,如果发生变化则重新计算数据。
对于悲观锁,无论是进行读操作还是进行写操作都会进行加锁操作。对于悲观锁,如果并发量较大则比较耗费资源,当然保证了数据的安全性。
在进行数据库操作时,写操作是默认加悲观锁的,而读操作是默认不加锁的,如果需要加锁,可以通过代码手动设置。
如果需要在进行数据库写操作时,加乐观锁,那么需要通过比较数据的版本来确定当前数据有没有被修改,如果被修改了,说明数据被人动了,就重新计算;如果没被修改,说明数据是初始的,就执行写入操作。
乐观锁除了数据库写操作的场景,还有多线程CAS操作的场景,CAS操作就是一种乐观锁,通过比较数据是否变动,来进行修改操作。如果CAS操作失败,则开始自旋等待。
2、共享锁和独占锁
共享锁和独占锁是从同一时刻是否允许多个线程持有该锁的角度来划分。
共享锁允许同一时刻多个线程进入持有锁,访问临界区资源。而独占锁就是通常意义上的锁,同一时刻只允许一个线程访问临界资源。对于共享锁,主要是指对数据库读操作中的读锁,在读写资源的时候如果没有线程持有写锁和请求写锁,则此时允许多个线程持有读锁。
在这里理解共享锁的时候,不是任意时刻都允许多线程持有共享锁的,而是在某些特殊情况下才允许多线程持有共享锁,在某些情况下不允许多个线程持有共享锁,否则,如果没有前提条件任意时刻都允许线程任意持有共享锁,则共享锁的存在无意义的。例如读写锁中的读锁,只有当没有写锁和写锁请求的时候,就可以允许多个线程同时持有读锁。这里的前提条件就是“没有写锁和写锁请求”,而不是任意时刻都允许多线程持有共享读锁。
ReentrantReadWriteLock中的读锁是共享锁,写锁是独占锁。
3、公平锁和非公平锁
这两个概念主要使用线程获取锁的顺序角度来区分的。
对于公平锁,所有等待的线程按照按照请求锁的先后循序分别依次获取锁。对于非公平锁,等待线程的线程获取锁的顺序和请求的先后不是对应关系。有可能是随机的获取锁,也有可能按照其他策略获取锁,总之不是按照FIFO的顺序获取锁。
在使用ReentrantLock的时候可以通过构造方法主动选择是实现公平锁还是非公平锁。
非公平锁会导致线程饿死,因为有可能存在线程竞争不到锁的情况发生,一直得不到执行。
4、可重入锁和不可重入锁
这两个概念是从同一个线程在已经持有锁的前提下能否再次持有锁的角度来区分的。
对于可重入锁,如果该线程已经获取到锁且未释放的情况下允许再次获取该锁访问临界区资源。此种情况主要是用在递归调用的情况下和不同的临界区使用相同的锁的情况下。
对于不可重入锁,则不允许同一线程在持有锁的情况下再次获取该锁并访问临界区资源。
对于不可重入锁,使用的时候需要小心以免造成死锁。
5、自旋锁和非自旋锁
这两种概念是从线程等待的处理机制来区分的。
自旋锁在进行锁请求等待的时候不进行wait挂起,不释放CPU资源,执行while空循环。直至获取锁访问临界区资源。适用于等待锁时间较短的情景,如果等待时间较长,则会耗费大量的CPU资源。而如果等待时间较短则可以节约大量的线程切换资源。
非自旋锁在进行锁等待的时候会释放CPU资源,可以通过sleep wait 或者CPU中断切换上下文,切换该线程。在线程等待时间较长的情况下可以选择此种实现机制。
除此之外还有一种介于两者之间的锁机制——自适应自旋锁。当线程进行等待的时候先进性自旋等待,在自旋一定时间(次数)之后如果依旧没有持有锁则挂起等待。在jvm中synchronized锁已经使用该机制进行处理锁等待的情况。
在工作中可以根据不同的情况选取合适的锁进行使用。无论使用哪种锁,其目的都是保证程序能够按照要求顺利执行,避免数据混乱情况的发生。