秋招之路8:JAVA锁体系和AQS抽象队列同步器

整个的体系图

悲观锁,乐观锁

是一个广义概念;体现的是看待线程同步的不同角度。

悲观锁

认为在自己使用数据的时候一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不被别的线程修改。
实现:关键字synchronized,接口Lock的实现类
适用场景:写操作多,先加锁可以保证写操作时的数据正确。

乐观锁

认为自己在使用数据的时候不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。
实现:CAS算法,例如AtomicInteger类的原子自增是通过CAS自旋实现
适用场景:读操作比较多,不加锁的特点能使读操作性能大幅提升。

悲观锁,乐观锁的执行流程

CAS算法

全名:Compare And Swap
也叫无锁算法:基于硬件原语实现,在不使用锁(没有线程被阻塞)的情况下,实现多线程之间的变量同步。
jdk中实现:java.util.concurrent包中的原子类(AtomicInteger)就是通过CAS来实现了乐观锁。

实现原理

算法设计的三个操作数
需要读写的内存值V
进行比较的值(版本)A
要写入的新值B

流程如下:
如果内存位置的值V与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B;
否则,不要更改该位置,只告诉我这个位置现在的值,只告诉我这个位置现在的值即可(具体//)。
具体图为:

基于CAS实现的原子类关键方法

AtomicInteger.getAndDecrementUnsafe.getAndAddInt
源码:稍后补

CAS存在问题

ABA问题

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,
但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化过。

ABA问题的解决思路就是使用版本号。
在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

循环开销时间长

只能保证一个共享变量的原子操作

从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,
可以把多个变量放在一个对象里来进行CAS操作。

自旋锁

它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。

但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

线程的上下文切换

一个重要的问题:线程切换为什么会耗资源耗时间?

  1. 线程切换的时候,CPU需要将此线程的所有执行状态保存起来,如线程编号,执行到的位置等,然后再去执行其它线程。
  2. CPU运行状态分为用户态和内核态。线程切换状态会使CPU运行状态从用户态转换到内核态。
    这里面有一篇:专门讲用户态和内存态的[https://www.cnblogs.com/maxigang/p/9041080.html]。

AQS队列同步器原理


ReentrantLock中的一个内部类

其具体是基于CLH队列实现。

具体原理

AQS 是构建锁或者其他同步组件的基础框架(如 ReentrantLock、ReentrantReadWriteLock、Semaphore 等), 包含了实现同步器的细节(获取同步状态、FIFO 同步队列)。AQS 的主要使用方式是继承,子类通过继承同步器,并实现它的抽象方法来管理同步状态。

  1. 维护一个同步状态 state。当 state > 0时,表示已经获取了锁;当state = 0 时,表示释放了锁。
  2. AQS 通过内置的 FIFO 同步队列来完成资源获取线程的排队工作:如果当前线程获取同步状态失败(锁)时,AQS 则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态。

AQS 内部维护的是** CLH 双向同步队列**

具体原理图

公平锁,非公平锁

在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。
而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。
所以,它们的差别在于非公平锁会有更多的机会去抢占锁。

总之就是:先来的先排队,先来的先获取资源,而非公平锁则无法提供这个保障。
其实对于非公平锁,只要线程进入了等待队列,队列里面依然是FIFO的原则,跟公平锁的顺序是一样的。

可重入锁

可重入锁作用:避免死锁.
场景:

public class Demo1 {
    public synchronized void functionA(){
        System.out.println("iAmFunctionA");
        functionB();
    }
    public synchronized void functionB(){
        System.out.println("iAmFunctionB");
    }
}

functionA()和functionB()都是同步方法,当线程进入funcitonA()会获得该类的对象锁,这个锁"new Demo1()",在functionA()对方法functionB()做了调用,但是functionB()也是同步的,因此该线程需要再次获得该对象锁(new Demo1())。其他线程是无法获该对象锁的。
可重入锁的作用就是为了避免死锁,java中synchronized和ReentrantLock都是可重入锁

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
其实,就是改变上图的state的状态++。

原文地址:https://www.cnblogs.com/whyaza/p/12336777.html

时间: 2024-08-30 02:44:22

秋招之路8:JAVA锁体系和AQS抽象队列同步器的相关文章

[Java并发] AQS抽象队列同步器源码解析--锁获取过程

要深入了解java并发知识,AbstractQueuedSynchronizer(AQS)是必须要拿出来深入学习的,AQS可以说是贯穿了整个JUC并发包,例如ReentrantLock,CountDownLatch,CyclicBarrier等并发类都涉及到了AQS.接下来就对AQS的实现原理进行分析. 在开始分析之前,势必先将CLH同步队列了解一下 CLH同步队列 CLH自旋锁: CLH(Craig, Landin, and Hagersten locks): 是一个自旋锁,能确保无饥饿性,提

秋招之路9:juc并发

j.u.c是java.util.concurrent的简称 通过查api,了解到是由以下这三个组成的. juc包图 锁的两种实现方式 java并发编程,关于锁的实现方式有两种: 1.基于synchronized关键字实现同步,jvm内置锁,也叫隐式锁,由jvm自动加锁和解锁 2.juc下的lock接口实现的更加灵活的锁的实现方式,也叫显示锁,需要手动加锁和解锁 重要分类 locks部分:显示锁(互斥锁和读写锁)相关: atomic部分:原子变量类相关,是构建非阻塞队列算法的基础,使用CAS实现:

腾讯2017秋招笔试_素数对 java程序

[编程题] 素数对 时间限制:1秒 空间限制:32768K 给定一个正整数,编写程序计算有多少对质数的和等于输入的这个正整数,并输出结果.输入值小于1000. 如,输入为10, 程序应该输出结果为2.(共有两对质数的和为10,分别为(5,5),(3,7)) 输入描述: 输入包括一个整数n,(3 ≤ n < 1000) 输出描述: 输出对数 输入例子1: 10 输出例子1: 2 Java源码  import java.util.Scanner; import java.util.TreeSet;

秋招之路1:ArrayList的底层实现原理

ArrayList 概述 ArrayList 是基于数组实现的,是一个动态数组 ArrayList 不是线程安全的,只能在单线程环境下:多线程使用ArrayList,应该考虑Collections.synchronizedList(List l)和concurrent并发包下的CopyOnWriteArrayList类 ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输: 实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速

秋招之路2: implements 与 extends ,抽象类总结

extends[继承类]的特点 子类拥有父类的非private属性,方法. 子类可以拥有自己的属性和方法,子类可以对父类进行扩展. 子类可以用自己的方式实现父类的方法. 减少了代码的冗余,即相同重复的代码可以放在父类里面,各自子类有不同的实现. 相关关键字 super:引用父类成员变量.局部变量.方法. this:引用本类成员变量,方法. 需要注意的是:类的成员变量与局部变量重名,类的成员变量将被隐藏,如果要使用类的成员变量,需要使用this引用之. implements[实现接口]的特点 1.

秋招之路7:全面JVM

jvm全景图 其中蓝色区域是线程独有的,黄色区域是线程共享的 分析方法 javap 命令 javap -c 可以对代码进行反汇编 里面的各种指令,可以用jvm指令手册一个一个查 一个线程的全景图 程序计数器:和计组一样,是存放下一条指令所在单元的地址的地方. 栈帧:一个方法对应一块栈帧内存区域.放自己方法里面各种变量用的. 其中局部变量表一般是放局部变量.操作数栈,一般是放操作过程中的常量. 局部变量表中的对象,存的是对象的地址,对象数据存在堆空间中. 动态链接:将符号引用转换为直接引用. 本地

最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 01.乐观锁 vs 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度,在Java和数据库中都有此概念对应的实际应用. 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制. 乐观锁适用于多

Java锁机制(二)

CAS无锁机制 CAS:Compare and Swap,即比较再交换. Java内存模型:JMM(Java Memory Model) 在内存模型当中定义了一个主内存,所有声明的实例变量都存在于主内存当中,主内存的数据会共享给所有线程,每一个线程有一块工作内存,工作内存当中主内存数据的副本 当更新数据时,会将工作内存中的数据同步到主内存当中 CAS无锁机制:本身无锁,采用乐观锁的思想,在数据操作时对比数据是否一致,如果一致代表之前没有线程操作该数据,那么就会更新数据,如果不一致代表有县城更新则

Java锁原理学习

Java锁原理学习 为了学习Java锁的原理,参照ReentrantLock实现了自己的可重入锁,代码如下: 先上AQS的相关方法: // AQS = AbstractQueuedSynchronizer, 抽象队列同步器 // 它提供了对资源的占用.释放,线程的等待.唤醒等接口和具体实现 // 它维护了一个volatile int state来代表共享资源的状态,和一个FIFO线程等待队列 // 获取排它锁 // 先尝试获取锁,如果获取不到则添加到等待队列 // 等待队列通过 LockSupp