乐观锁和悲观锁
- 定义
- 乐观锁:每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
- 悲观锁:每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。
- 使用场景:
- 乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。 例如版本号控制,时间戳控制。
- 悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。例如数据库的行锁。
自旋锁
- 定义:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁。
- 优点:
- 非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)
- 应用场景:
在线程持有锁时间较短的情况下可以使用自旋锁节省上下文切换开销,但是对于长时间持有的锁不适宜设计为自旋,因为自选时CPU忙等,耗费大量算力。 - 变种:自适应自旋锁,可以根据上一次线程持有锁的时间来确定自旋一段时间或者直接阻塞。
互斥锁和条件锁(条件变量)
可以参考线程同步。
其他锁
- 可重入锁和不可冲入锁:一个线程中多个流程能不能获取同一把锁,或者在递归情况下能不能避免死锁。
- 多个线程竞争时要不要排队?排队称为公平锁,先尝试插队,若失败再排队称为非公平锁。
- 轻量级锁和重量级锁(待研究)