锁
锁的概念
锁是计算机协调多个进程或线程并发访问某一资源的机制,它主要用于同步并发访问共享资源的多个线程或进程,以防止数据不一致或竞争条件的发生,锁的使用场景有以下几种:
- 多线程编程:在多线程环境中,多个线程可能同时访问和修改共享数据。使用锁可以确保在同一时间只有一个线程可以访问和修改数据,从而避免数据不一致和竞争条件。
- 并发编程:在并发编程中,多个进程可能同时访问共享文件、数据库连接等资源。使用锁可以确保同一时间只有一个进程可以访问资源,防止数据损坏或不一致。
- 分布式系统:在分布式系统中,多个节点可能同时访问和修改共享资源。使用分布式锁可以确保同一时间只有一个节点可以访问和修改资源,实现数据的最终一致性。
锁的类型
root((锁的类型)) 共享锁 排他锁 悲观锁 乐观锁 行锁 表锁 自旋锁 公平锁 非公平锁共享锁
共享锁(Shared Lock,通常简称为S锁)是数据库管理系统(DBMS)中的一种锁机制,用于控制对共享资源的并发访问。当一个事务需要读取某个资源时,它会获取该资源的共享锁,以确保在事务结束之前,其他事务不能修改该资源,但可以同时获取该资源的共享锁来读取它。这允许多个事务同时读取同一资源而不会相互干扰。
然而,如果某个事务已经持有了一个资源的共享锁,并且另一个事务想要修改这个资源(即获取排他锁或独占锁),那么第二个事务将被阻塞,直到第一个事务释放其共享锁。同样地,如果一个事务持有排他锁,其他事务既不能获取该资源的共享锁也不能获取排他锁。
排他锁
排他锁(Exclusive Lock),通常简称为X锁,是数据库管理系统(DBMS)中用于控制并发访问的一种锁机制。当一个事务需要对某个资源进行修改(如UPDATE、DELETE操作)时,它会请求获取该资源的排他锁。一旦获得了排他锁,该事务就可以独占性地访问这个资源,而其他事务则无法再获取该资源的任何锁(包括共享锁和排他锁),直到第一个事务释放了排他锁。
排他锁的主要目的是确保在数据被修改的过程中,其他事务不会对其进行读取或修改,从而避免了脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)等并发问题。
在数据库操作中,排他锁通常是自动获取的。当事务执行DML(数据操纵语言)语句时,数据库管理系统会根据需要自动在相应的资源上加上排他锁。例如,在MySQL中,当事务执行UPDATE或DELETE操作时,它会自动在受影响的行上加上排他锁。
需要注意的是,排他锁不仅会影响其他事务对同一资源的访问,还可能导致死锁(Deadlock)的发生。死锁是指两个或更多的事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法向前推进。因此,在使用排他锁时,需要谨慎考虑并发访问的需求和可能的并发问题,并采取相应的策略来避免死锁的发生。
悲观锁
悲观锁(Pessimistic Lock)是一种并发控制机制,它基于对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度的策略,有以下特点:
- 悲观锁具有强烈的独占和排他特性。
- 它假设最坏的情况,即数据在处理过程中很可能会被其他事务修改。
因此,在整个数据处理过程中,悲观锁会将数据处于锁定状态,以确保数据的完整性和一致性。悲观锁的实现通常依靠数据库提供的锁机制。这是因为只有数据库层提供的锁机制才能真正保证数据访问的排他性。 在数据库中,悲观锁可以通过SQL语句(如SELECT … FOR UPDATE)来实现,这会将检索到的数据行加锁,防止其他事务对其进行修改。 在Java等编程语言中,悲观锁的实现可能涉及使用同步块(synchronized blocks)或Lock接口等机制。
悲观锁适用于写操作频繁、并发冲突严重的场景。由于它总是假设最坏的情况,因此能够有效地避免数据的不一致性和脏读等问题。然而,过度使用悲观锁可能会导致性能下降和死锁等问题。因此,在选择是否使用悲观锁时需要根据具体的业务场景和需求进行权衡。
乐观锁
乐观锁(Optimistic Lock)是一种并发控制机制,它基于对数据被外界修改持保守态度的策略, 认为最坏的情况不会发生,乐观锁不是数据库自带的,需要我们自己去实现,有以下特点:
- 乐观锁假设在大多数情况下,多个线程或事务之间不会发生冲突。
- 读取数据时,每个线程或事务会获得一个标识符(如版本号或时间戳),此标识符通常与数据一同被读取出来。
- 在提交修改之前,线程或事务会比较当前标识符与之前读取的标识符是否相等。如果相等,则提交成功;否则,说明数据已被其他线程或事务修改,需要进行冲突处理。
乐观锁适用于读操作频繁而写操作较少的场景,因为它可以减少锁的使用,提高并发性能。在高并发的系统中,乐观锁能够避免长时间持有锁带来的性能开销和死锁风险。
行锁
行锁(Row Lock)是一种数据库锁机制,用于控制对数据库表中单行数据的并发访问。以下是关于行锁的详细解释:
定义 行锁是对数据库表中特定行进行加锁的机制,当一个事务需要对表中的某一行进行修改时,它会在该行上加上一个锁,以阻止其他事务对该行进行并发访问,有以下特点:
- 锁定范围:行锁仅锁定表中的一行数据,而不是整个表。这意味着其他事务仍然可以访问表中的其他行,不受锁定行的影响。
- 粒度:行锁的粒度很细,只影响被锁定的那一行。这使得在高并发场景下,多个事务可以同时访问表中的不同行,从而提高系统的并发性能。
- 适用场景:行锁适用于高并发读写的场景,特别是当需要修改的数据量相对较少时。它允许多个事务同时访问表的不同行,降低了锁的争用。
在高并发场景下,行锁可以有效地提高系统的并发性能。然而,如果事务持有行锁的时间过长或者频繁地请求行锁,可能会导致锁争用和性能下降。此外,如果多个事务试图同时修改同一行数据,可能会导致死锁的发生。
表锁
表锁(Table Lock)是数据库管理系统(DBMS)中用于控制对数据库表进行并发访问的一种锁机制。与行锁不同,表锁会锁定整张表,而不仅仅是表中的某一行。当事务需要对表中的数据进行修改时,它可能会请求对整个表加锁,以防止其他事务并发地访问该表中的数据,有以下特点:
- 锁定范围:表锁锁定的是整张表,而不是表中的某一行或某些行。
- 并发性能:由于锁定的是整张表,因此在表锁被持有期间,其他事务无法对该表进行写操作(如UPDATE、DELETE或INSERT),但可能仍然可以读取表中的数据(这取决于数据库的隔离级别和锁的实现)。因此,在高并发的读写场景中,表锁可能会成为性能瓶颈。
- 简单性:表锁的实现相对简单,因为数据库管理系统不需要跟踪表中哪些行被锁定,哪些行没有被锁定。
表锁的实现方式简单,对于某些操作(如全表扫描或批量更新),表锁可能比行锁更高效,但是并发性能较差。在高并发的读写场景中,表锁可能会导致严重的性能瓶颈,并且容易出现死锁,当多个事务相互等待对方释放表锁时,可能会导致死锁的发生。
自旋锁
自旋锁(Spinlock)是一种用于保护共享资源的简单锁。当一个线程试图获取一个已经被其他线程持有的自旋锁时,该线程不会立即阻塞(进入睡眠状态),而是会进入一个忙等待(busy-waiting)的循环,持续检查锁是否可用。如果锁可用,则立即获取锁并继续执行;如果锁不可用,则继续循环检查,直到锁被释放。
自旋锁适用于锁持有时间较短的情况,因为在这种情况下,线程等待锁的时间较短,因此使用自旋锁可以避免线程切换的开销,从而提高性能。然而,如果锁持有时间较长,使用自旋锁可能会导致CPU资源的浪费,因为线程会持续占用CPU资源等待锁,而无法执行其他任务。
需要注意的是,自旋锁并不是所有场景下都是最佳选择。在某些情况下,使用其他类型的锁(如互斥锁、读写锁等)可能更为合适。
公平锁
公平锁(Fair Lock)是一种在并发环境中用于控制线程对共享资源的访问顺序的锁机制。其核心特点是确保线程按照请求锁的顺序来获取锁,类似于 FIFO,先来后到。
非公平锁
非公平锁(Non-fair Lock)是一种在并发环境中用于控制线程对共享资源的访问顺序的锁机制。其核心特点是允许线程获取锁的时机不固定,即允许线程获取锁的时机不按照请求锁的顺序来获取锁。
可重入锁
可重入锁(Reentrant Lock)是一种线程安全的锁,它允许一个线程对同一个锁进行多次加锁和解锁操作。当一个线程请求一个已经持有的锁时,该锁会保持其状态,直到请求的线程释放锁,如 Sychronized 锁。