锁
基础概念
作用:
锁主要用于控制对共享资源的访问,以防止并发线程之间的冲突。
实现:
一、抢锁,为保证抢锁的安全性,避免同时抢到锁的可能,一般是通过:
1.CAS1(Compare And Swap)来保证的,CAS 是硬件层面的不可中断操作。
2.外部应用单线程的机制,如Redis(分布式锁)。
二、线程等待,没抢到锁的线程会进入等待状态,等待状态下可以是:
1.调用 Object 的 wait 释放资源进入等待。
2.Redis 的消息订阅,利用NIO进入IO等待状态释放资源(Redis 分布式锁)。
3.持续轮询,直到获得锁,这种方式虽然简单,但可能导致 CPU 资源的浪费。
三、锁释放通知,等待锁的线程可能会进入多种状态,有些需要一定的通知机制来唤醒它们:
1.本地的 wait 需要 Object 的 notify、notifyAll 方法通知。
2.发布消息,通知订阅者,由于 NIO 的机制可能会分配与原来不同的线程(Redis 分布式锁)。
锁的种类
实现类型: 偏向锁2:synchronized 的一种实现机制,使用CAS抢锁,单线程获取锁时直接记录线程 ID,重入时不再 CAS 操作。 轻量锁(自旋锁):使用 CAS 抢锁,等待的线程进入轮询抢锁状态,会造成 cpu 资源浪费。 重量锁:使用 CAS 抢锁,等待的线程调用 wait 进入挂起状态。 分布式锁:使用外部应用(如 Redis)控制抢锁,等待的线程可选择轮询或消息订阅的方式进行等待。 控制策略: 悲观锁:范围锁,针对一段业务的范围锁,同一时间只能有一个线程执行业务。 乐观锁:单点锁,针对业务中某个点的锁,通常是利用数据库的版本控制,有点类似 CAS,会在尝试更新时检查资源状态。atomic 下的类也是。 公平性: 公平锁:使用了 FIFO 保证先进先出,确保线程获取锁的顺序。 非公平锁:没有措施,醒了就大家一起哄抢,先醒先得,所以存在插队的情况。 功能性: 重入锁:可以重入的锁,使用了重入计数器,每进一次加1,出去时减1,为0时释放锁。 读写锁:允许多个线程并发读取,但在有写操作时不能读取,也不能进行写入。适用于读多写少的场景,减少锁的等待时间。
锁的实现
Synchronized
Synchronized 是 Java 中的一个关键字,用于实现线程同步,确保在多线程环境下对共享资源的访问是线程安全的。 Synchronized 有多种锁机制: 偏向锁:当只有一个线程获取锁时会立即获取到偏向锁。 轻量锁:当有第二个线程尝试获取锁时,锁会升级为轻量锁。 重量锁:当第二个线程长时间没获取到锁,或者有多个线程获取锁时升级为重量锁。 基本特性: 独占锁:同一时间只能有一个线程持有该锁,其他线程必须等待。 可重入性:一个线程在获取锁后,如果在锁定的代码块中再次调用同步方法,不会被阻塞,因为它可以重入。 自动释放:当线程离开同步代码块或方法时,锁会自动释放,不需要显式解锁。 使用场景: 实例方法:锁定当前实例对象 (this),使当前对象的所有 synchronized 方法同步。 静态方法:锁定整个类的 Class 对象,使当前类的所有 synchronized 静态方法同步。 关键字:偏向锁,轻量锁,重量锁,重入锁,非公平锁,悲观锁
ReentrantLock
ReentrantLock 是 Java 中的一种灵活且可重入的锁实现,由 java.util.concurrent.locks 包提供。它在功能上类似于 synchronized,但提供了更高的可控性和灵活性,例如支持公平锁、非公平锁、可中断锁、超时等待等功能。 基本特性 可重入性:线程可以多次获取同一个 ReentrantLock 锁,而不会发生死锁,每次获取锁会增加重入计数,释放锁时会递减,直到为 0 时完全释放。 公平性:可以通过构造参数指定是否为公平锁,公平锁按照请求顺序获取,非公平锁允许“插队”,可能提升吞吐量。 可中断:在等待获取锁的过程中可以中断线程。 支持条件变量:与 synchronized 的 wait() 和 notify() 相似,ReentrantLock 提供了 Condition 类,通过 newCondition() 方法可以生成多个条件变量,并设置等待超时时间,实现更加灵活的线程间通信。 公平锁内部实现 ReentrantLock 是通过 AbstractQueuedSynchronizer(AQS)实现的,它基于 CLH 队列管理锁的公平性和排队等待,通过 CAS 操作管理锁状态,使其成为线程安全的独占锁。 关键字:重量锁,重入锁,公平锁/非公平锁,悲观锁,可中断,超时等待,AbstractQueuedSynchronizer
ReentrantReadWriteLock
ReentrantReadWriteLock 是 Java 中提供的一种读写锁实现,它允许多个线程同时读共享资源,但在写入时只允许一个线程进行写操作。这种设计可以提高并发性能,尤其在读操作远多于写操作的场景中。 主要特性: 读写分离:允许多个读线程并发访问共享资源,但在写线程进行写操作时,其他读线程和写线程必须等待。 可重入:ReentrantReadWriteLock 是可重入的,允许同一个线程多次获得读锁或写锁。 关键函数: 读锁(ReadLock):通过 readLock() 方法获得,允许多个线程并发读取数据。 写锁(WriteLock):通过 writeLock() 方法获得,只允许一个线程写入数据。 关键字:读写锁,重量锁,重入锁,公平锁/非公平锁,悲观锁,可中断,超时等待,AbstractQueuedSynchronizer,双等待队列(读写)
AtomicInteger
AtomicInteger 是 Java 提供的一种原子类,属于 java.util.concurrent.atomic 包。它主要用于实现线程安全的整数操作,能够在多线程环境下避免使用传统的锁机制。 主要特性: 原子性:AtomicInteger 的操作是原子的,即对它的读、写、和修改操作不会受到其他线程的干扰,保证线程安全。 无锁实现:通过使用底层的原子操作(如 CAS - Compare And Swap),AtomicInteger 可以在不加锁的情况下实现线程安全。 性能优越:由于避免了锁的开销,AtomicInteger 在高并发场景下性能更好,适合于计数器、状态标志等应用场景。 总结 AtomicInteger 是 Java 提供的高效原子性整数操作类,适用于高并发的计数和状态管理场景。通过原子操作,它提供了线程安全的访问方式,减少了锁的使用,从而提高了程序的性能。需要注意的是CAS操作可能存在失败的可能,它会一直重试直到操作成功。 关键字:CAS,无锁
Redisson
Redisson 锁的机制是建立在 Redis 的原子操作和数据结构之上的,以实现高效的分布式锁。 实现方式简述: 1.利用 Redis 单线程操作的特性,通过 SETNX 命令来达到类似于 CAS 的效果。 2.使用 Redis 的发布/订阅功能来通知等待的线程。 锁的类型 分布式锁:基本的锁实现,确保在分布式环境中只允许一个线程持有锁。 公平锁:保证按照请求锁的顺序来获取锁,使用 FIFO(先进先出)队列。 非公平锁:没有请求顺序的保证,任何线程都可以在获取锁时插队。 可重入锁:允许同一线程多次获得锁,使用计数器来管理重入。 红锁:一种由 Redis 组成的分布式锁算法,允许在多个 Redis 实例中获得锁,增强了锁的可靠性。 总结 Redisson 的锁机制利用 Redis 的特性,提供了灵活而强大的分布式锁实现。通过原子操作、超时机制、消息订阅等手段,Redisson 能够确保在分布式环境中实现高效且安全的锁管理。 关键字:分布式锁,重量锁,重入锁,公平锁/非公平锁,悲观锁,超时等待
总结
锁的设计核心就是抢锁(CAS)等待(Wait)->通知并唤醒(Notify),不同实现方式基本大同小异。另外根据业务场景引入了可重入、可中断、可设置等待、可续约、区分读写、公平性等。
Redission详细
1.没得 2.锁的实现机制 2.1 获取锁 使用 SETNX:在 Redis 中,获取锁的基本操作是使用 SETNX 命令(Set if Not eXists)。这个命令在键不存在时设置一个值并返回 1,如果键已经存在,则返回 0。Redisson 在获取锁时,会尝试使用 SETNX 命令。 锁的唯一性:获取锁时,Redisson 会为每个锁设置一个唯一的标识(通常是线程 ID 和时间戳组合),以确保锁的唯一性和防止死锁。 2.2 锁的超时 设置过期时间:获取锁成功后,Redisson 会为锁设置一个过期时间,防止因未释放锁而导致的死锁。这一机制确保锁在一段时间后自动释放,避免长时间阻塞。 2.3 释放锁 原子性释放:释放锁的过程也是通过 Redis 的原子操作来完成。只有锁的持有者才能释放锁,Redisson 使用 Lua 脚本来确保这一操作的原子性。Lua 脚本中会检查当前锁的持有者是否是调用释放锁的线程,只有验证通过后才会删除锁。 3. 等待和唤醒机制 消息订阅:当锁被其他线程释放时,Redisson 可以通过 Redis 的发布/订阅功能来通知等待的线程。等待线程会订阅某个频道,锁的持有者在释放锁时会向该频道发送消息,等待线程在收到消息后进行抢锁尝试。 NIO 支持:Redisson 在处理等待和唤醒时使用 NIO(非阻塞 IO)来提高性能,避免线程阻塞和不必要的上下文切换。 4. 公平性 公平锁和非公平锁:Redisson 的实现支持公平锁和非公平锁。公平锁使用 FIFO 队列来确保请求锁的线程按顺序获取锁,而非公平锁允许线程在获取锁时插队。 5. Redisson 锁的优缺点 优点 高可用性:由于是基于 Redis 的分布式锁,Redisson 可以在多个节点间进行锁的管理。 灵活性:支持多种类型的锁,满足不同场景的需求。 自动过期:避免死锁的发生,通过设置超时时间,锁会自动释放。 缺点 性能开销:虽然 Redis 操作快速,但在高并发情况下,获取和释放锁的操作仍会带来一定的性能开销。 网络延迟:分布式锁依赖于网络,如果 Redis 节点发生故障或者网络不稳定,可能会导致获取锁的失败。 6.红锁的基本原理 红锁的实现过程如下: 准备多个 Redis 实例:红锁需要在多个 Redis 实例上运行,通常至少需要五个实例来保证高可用性。这样可以避免因为单点故障导致的锁失效。 获取锁的步骤: 客户端请求锁时,生成一个唯一的锁标识(通常是 UUID)和过期时间。 客户端依次向每个 Redis 实例发送锁请求。 只有在成功获取到大多数实例的锁时,锁才算获取成功。例如,在五个实例中,至少需要获取到三个实例的锁。 锁的过期:每个 Redis 实例的锁都有一个过期时间,以防止死锁的情况发生。 释放锁:当客户端完成操作后,需要释放锁。释放时,客户端会向所有 Redis 实例发送解锁请求,只有锁的持有者(即持有锁的唯一标识)才能释放锁,以确保锁的安全性。
问题
1.锁的实现 2.Atomic下的类的实现 3.ReentrantLock的实现 4.Redis分布式锁的实现