锁:是一种用于控制多线程访问共享资源的机制。它可以防止多个线程同时访问共享资源,避免竞态条件和线程安全问题的发生。
java中的主要锁机制:
可重入锁是一种支持重复进入的锁,也称为递归锁。当一个线程获得了一个可重入锁后,可以再次获得该锁而不会被阻塞。可重入锁在同一线程多次获取锁时会维护一个计数器,每次获取锁计数器加1,释放锁时计数器减1,只有当计数器减为0时才算完全释放了锁。
可重入锁在多层嵌套调用时非常有用,避免了死锁和线程饥饿问题。例如,一个方法A获得了可重入锁后,可以调用另外一个方法B,而方法B也可以再次获取该可重入锁,而不会因为已经由方法A获取了锁而被阻塞。这样可以确保在同一线程内,嵌套调用多个方法时,都可以获取到所需的锁而不会出现死锁情况。
可重入锁的实现可以使用syncrhonized关键字或者Lock接口的实现类,如ReentrantLock。在Java中可重入锁主要用于解决线程同步问题,确保多线程能够正确有序地访问共享资源
1.Synchronized锁,jvm层面的锁(内置锁,也称为监视器锁),可重入锁,对象头 markword中锁信息,线程信息,
锁可以根据其特性和用途进行分类。以下是对常见锁的分类和说明:
1. 乐观锁(Optimistic Locking)和悲观锁(Pessimistic Locking):
- 乐观锁假设多个线程间不会发生冲突,只在更新操作时进行实际的冲突检测,以避免锁的开销。
- 悲观锁假设多个线程间会发生冲突,因此在进行操作之前先获取锁,保证数据的一致性。
2. 共享锁(Shared Lock)和独占锁(排它锁也叫Exclusive Lock):
- 共享锁允许多个线程同时获得该锁并共享资源,主要用于读取操作。
- 独占锁只允许一个线程获得该锁并独占资源,主要用于写入操作。
3. 公平锁(Fair Lock)和非公平锁(Unfair Lock):
- 公平锁按照线程请求锁的顺序获得锁,避免饥饿现象,但可能导致性能下降。
- 非公平锁允许线程在任何时间获得锁,不按照请求顺序,可能会导致某些线程长时间无法获取锁。
4. 偏向锁(Biased Locking)、轻量级锁(Lightweight Lock)和重量级锁(Heavyweight Lock):
- 偏向锁是一种针对无竞争情况下的优化手段,通过标记对象头,使得同一线程多次获取同一个锁时不需要额外的开销。
- 轻量级锁通过CAS操作来避免线程的阻塞和唤醒,适用于短期的同步操作。
- 重量级锁是操作系统级别的锁,需要进行线程的阻塞和唤醒操作,适用于长期的同步操作。
5. 可重入锁(Reentrant Lock)和不可重入锁(Non-reentrant Lock):
- 可重入锁允许线程多次获取同一个锁,避免死锁,可以嵌套调用被锁保护的方法。
- 不可重入锁不允许线程多次获取同一个锁,在第一次获取锁后再次尝试获取会造成死锁。
是Java中用于构建各种同步机制的一个基础框架,它通过维护一个等待队列和状态变量,实现了线程之间的协调和通讯
AQS的实现基于一个FIFO(先进先出)的等待队列,它维护了一组等待线程【链表+线程】,并提供了一些方法来控制线程的访问和等待。AQS的关键方法是`acquire()`和`release()`,它们用于获取和释放资源的状态。
AQS使用一个整型变量(称为`state`)来表示资源的状态,这个状态可以被多个线程共享。当一个线程尝试获取资源时,它首先会通过`acquire()`方法获取锁,如果锁已经被其他线程占用,那么当前线程就会进入等待队列,并被阻塞。当锁被释放时,AQS会从等待队列中选择一个线程唤醒,并允许它获取锁。
另外,AQS还提供了一些其他的方法,如`tryAcquire()`和`tryRelease()`,它们用于非阻塞地尝试获取和释放资源。AQS还提供了一些条件变量的支持,通过`newCondition()`方法可以创建一个与AQS相关联的条件对象,用于实现线程之间的等待和唤醒机制。
ReentrantLock,,Semaphore,ReentrantReadWriteLock,CountDownLatch 等
共享锁是一种用于实现多线程并发控制的锁机制,它允许多个线程同时访问被保护的资源,但同时也保证了对资源的访问是有序和安全的。
共享锁的实现通常有两种常见方式:读写锁和信号量。
1. 读写锁:读写锁允许多个线程同时读取共享资源,但只有一个线程可以进行写操作。它通过维护一个读计数器和一个写标识来实现。当一个线程请求读锁时,如果没有写线程持有写锁,则允许线程获取读锁并增加读计数器;当一个线程请求写锁时,如果没有其他线程持有读锁或写锁,则允许线程获取写锁,并设置写标识为被占用。读写锁的实现可以确保多个读操作之间的并发,但在写操作期间会阻塞读操作,以保证数据的一致性和完整性。
2. 信号量:信号量是一种由整数值表示的计数器。它允许多个线程同时获取资源,但要求在获取资源之前必须先获得信号量。一个线程获得信号量后,计数器的值会减一,表示资源被占用。如果计数器的值为0,表示资源已经被占用完,需要等待其他线程释放资源后才能继续获取。通过信号量,可以实现对共享资源的有序访问和控制。
总体而言,共享锁通过限制同时访问共享资源的线程数量来保证线程安全。它允许多个线程同时读取资源,但只允许一个线程进行写操作或只允许一定数量的线程同时访问资源。通过合理的调度和控制,共享锁可以保证线程对资源的访问是有序和安全的。
java中锁的实现方式:
1. synchronized关键字:synchronized是Java内置的一种锁机制,它能实现原子性操作和线程安全。使用synchronized修饰的方法或代码块,在同一时刻只能被一个线程访问,其他线程需要等待锁释放后才能继续操作。
synchronized锁的升级过程
为了提交锁的获取和释放效率,jdk1.6之后引入了锁升级。
无锁:这是对象未被任何线程锁定时的初始状态。此时没有线程持有锁,所有访问同步代码块的线程都处于无锁竞争状态。
偏向锁:当一个线程首次获得对象锁时,JVM会将锁设置为偏向锁,并将锁对象的Mark Word中的线程ID设置为当前线程的ID。后续当这个线程再次请求相同的锁时,只需检查Mark Word中的线程ID是否与当前线程ID一致,如果一致则直接进入同步代码块,无需进行额外的同步操作。偏向锁适用于大多数情况下只有一个线程访问同步资源的场景。
轻量级锁:当有第二个线程尝试获取已被偏向锁锁定的对象时,偏向锁失效,JVM会尝试升级为轻量级锁。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”。轻量级锁适用于线程交替执行同步代码块的场合,如果存在同一时间访问同一锁的场合,轻量级锁会膨胀为重量级锁。
重量级锁:当轻量级锁失败后,JVM会升级为重量级锁。重量级锁需要向内核申请额外的锁资源,涉及到用户态和内核态的切换,效率较低。重量级锁适用于多线程竞争激烈的场合。
用户态(User Mode)指的是操作系统执行的应用程序所处的运行环境。在用户态下,应用程序可以直接访问自己的进程空间以及一些受限资源,如文件、网络等。在用户态下运行的程序无法直接访问操作系统的底层资源,必须通过操作系统提供的系统调用来间接访问。
内核态(Kernel Mode)指的是操作系统内核执行的时候所处的运行环境。在内核态下,操作系统拥有对所有硬件和资源的完全控制权,并且可以执行特权指令,访问系统的底层资源。内核态下的代码运行在特权级最高的CPU模式下,拥有更高的权限和更广的访问范围。
用户态和内核态之间的切换是通过操作系统的机制来实现的,例如异常处理机制和系统调用等。当用户态的程序需要访问内核态的资源或执行特权操作时,需通过系统调用将控制权转移到内核态,内核完成相应的操作后再将控制权返回给用户态。这样设计的目的是为了保护操作系统的稳定性和安全性,防止用户程序直接滥用操作系统的资源。
不同类型锁的优缺点
- 无锁:没有同步开销,但无法保证数据的一致性和安全性。
- 偏向锁:适用于单线程环境,减少了同一线程获取锁的代价,提高了性能。
- 轻量级锁:适用于线程交替执行同步代码块的场合,减少了操作系统交互,提高了效率。
- 重量级锁:适用于多线程竞争激烈的场合,但效率较低。
2. ReentrantLock类:ReentrantLock是Java提供的一个可重入锁的实现类[悲观锁]。它使用显式锁的方式,需要手动进行锁的获取和释放。可以通过lock()方法获取锁,并通过unlock()方法释放锁。相比synchronized关键字,ReentrantLock提供了更多的高级特性,如可定时、可中断的锁等。
1. 独占锁:ReentrantLock通过使用一个volatile修饰的state变量来表示锁的状态。当state为0时,表示锁是可获取的;当state为1时,表示锁被某个线程持有。在可重入锁的情况下,如果一个线程再次获取锁,它需要记录获取的次数,将state的值增加。而当线程释放锁时,需要将state的值减少。这样可以解决同一个线程重复获取同一个锁的问题。
2. 同步队列:当一个线程尝试获取锁时,如果锁已经被其他线程持有,它会被放入一个同步队列中等待。同步队列采用一个FIFO(先进先出)的顺序来管理等待线程,按照线程等待锁的顺序进行唤醒。同时,在同步队列中,每个节点都包含了前驱节点和后继节点的引用,使得线程可以方便地等待和唤醒。
基本的锁获取和释放过程如下:
- 锁获取:当一个线程尝试获取锁时,它首先会通过CAS(Compare and Swap)操作尝试将state的值从0修改为1。如果CAS操作成功,表示该线程成功获取到了锁。如果CAS操作失败,则表示锁已经被其他线程持有了。此时,该线程会被放入同步队列中等待锁。
- 锁释放:当一个线程释放锁时,它首先会通过CAS操作将state的值从1修改为0。如果CAS操作成功,表示该线程成功释放了锁。然后,它会唤醒同步队列中的后继节点,让下一个等待锁的线程能够获取锁。
- 等待和唤醒:当一个线程被放入同步队列中等待锁时,它会通过自旋和LockSupport.park()方法进行等待。当锁被释放并且该线程在同步队列中的前驱节点是头节点时,它会被唤醒,继续尝试获取锁
3. ReadWriteLock接口:ReadWriteLock是Java提供的一种读写[共享锁]】,用于提高读操作的并发性。它允许多个线程同时读取共享资源,但只允许一个线程进行写操作,共享锁。具体实现类ReentrantReadWriteLock通过维护读锁和写锁来实现。
4. LockSupport类:LockSupport类提供了一种基于许可的线程阻塞原语,用于实现锁和其他同步组件。它的park()方法可以使线程进入休眠状态,unpark()方法可以唤醒指定的线程。通过调用park()和unpark()方法,可以实现类似于synchronized和wait-notify的功能。
这些锁的实现方式各有优劣,应根据具体需求选择合适的锁机制。synchronized是最基本的锁,使用方便,适用于大多数场景。ReentrantLock提供了更多的灵活性和高级特性。ReadWriteLock适用于读多写少的场景,可以提高并发性。LockSupport是一种更底层的锁机制,用于实现更复杂的同步操作。
参考链接:AQS简单介绍与使用-CSDN博客
533

被折叠的 条评论
为什么被折叠?



