赞
踩
AbstractQueuedSynchronizer,JDK提供的一个抽象类;
这些全是 AbstractQueuedSynchronizer 的子类; AbstractQueuedSynchronizer 是 JDK 中用来构建同步并发的基础组件;
AQS 中有一个比较重要的同步变量:private volatile int state // 同步状态;
不管是 JDK 还是我们自己,在实现一个同步类的时候,都要围绕着这个 state 来做文章,修改它的值来表示当前的同步状态发生了变化;
为什么我们在使用各种同步类的时候而没有感受到 AQS 的存在呢?因为 AQS 在使用方式上采用的是继承的方式,而且是在同步工具类的内部定义了一个静态内部类来继承 AQS,这个同步工具类把内部类暴露的方法进行了一层封装,使我们感受不到 AQS 的存在;
所以说 AQS 采用的是 模板方法 的设计模式来实现;
在阎宏博士的《JAVA与模式》一书中开头是这样描述模板方法(Template Method)模式的:
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
代码示例:
模板抽象类
- public abstract class AbsCar {
- public abstract void makeLight(); // 造车灯
- public abstract void makeDoor(); // 造车门
- public abstract void makeGlass(); // 造车玻璃
- public void make() {
- makeLight();
- makeDoor();
- makeGlass();
- }
- }
抽象实现子类1
- public class AudiCar extends AbsCar {
- @Override
- public void makeLight() {
- System.out.println("make audi light");
- }
- @Override
- public void makeDoor() {
- System.out.println("make audi door");
- }
- @Override
- public void makeGlass() {
- System.out.println("make audi glass");
- }
- }
抽象实现子类2
- public class BMWCar extends AbsCar {
- @Override
- public void makeLight() {
- System.out.println("make bmw light");
- }
- @Override
- public void makeDoor() {
- System.out.println("make bmw door");
- }
- @Override
- public void makeGlass() {
- System.out.println("make bmw glass");
- }
- }
抽象实现子类。。。
具体调用
- public class MakeCar {
- public static void main(String[] args) {
- AbsCar car = new AudiCar();
- AbsCar car1 = new BMWCar();
- car.make();
- car1.make();
- }
- }
Android 源码中的自定义 View 就是模板方法模式,onDraw() onMeasure() onLayout();
如果我们需要自己实现一些锁,那么就需要遵照这个模板方法模式来实现;比如你想实现独占锁,那么就需要实现 AQS 中的 tryAcquire,如果你想实现共享同步锁,那么就实现 AQS 中的 tryAcquireShared 方法;
拿到锁的线程在执行的时候,另外的线程需要排队,那么所有要排队的线程都打包成一个QNode(所有线程都会放入一个链表(QNode)中)
QNode包含三个元素:1、当前线程本身,2、myPred,3、locked;
myPred:链表上的指针,指向前驱节点;
locked:表示当前需要获得锁;
假设QNode中的线程A要获得锁,于是采用类似 CAS 的算法,把自己加在已有的链表的尾巴上,让 myPred 指向前一个节点,同时 locked 变成 true
线程A把自己挂在链表的尾部,形成一个新的尾节点,这样线程 A 也支持能锁了;其他线程也是重复这样的操作;
那么节点 A 和节点 B 怎么才能拿到锁呢?A 节点和 B 节点都有一个指针(myPred)指向前一个节点,myPred 本身会不停的自旋,检测前一个节点有没有释放掉锁,如果前一个节点的 locked = false 了,说明前一个节点已经把锁释放了,当前节点的线程可以拿到锁了;
myPred不会一直自旋下去,而是自旋一定的次数(一般是2-3次)之后,如果还没拿到锁,就会把当前线程挂起,进入阻塞状态,并不会一直不停的自旋下去;
自旋逻辑源码如下:
compareAndSetTail 进行入队操作,如果入队不成功,就会调用 enq() 进行自旋操作;
当自旋一定的次数之后,如果还不成功,就会挂起;
公平锁说的是:老老实实的在 QNode 链表队尾排队;
非公平锁说的是:可以插队;
显示锁中的公平锁(FairSync)和非公平锁(NonFairSync)的实现;
FairSync -> 拿锁之前会判断下当前链表中是不是有元素在等待;
NonFairSync -> 拿锁之前不判断,直接进行 compareAndSetState 进行拿锁;
什么是可重入? 可以递归调用就是可重入;
概念:现代 CPU为了提高运行速度,引入了一批 Cache(L1 2 3)「高速缓存区」,为了管理这批 Cache,Java 中单独提出了 Java 内存模型。预先将 CPU 需要的数据读取到 Cache 中;
三级缓存:
工作内存、主内存 这两个抽象概念是很多存储设备的一个综合,比如说 这个工作内存包括了 CPU 寄存器、CPU 高速缓存、甚至还包括了主内存;工作内存 99% 包括了 CPU 高速缓存,1%可能包括了主内存;
例如:多线程执行累加操作,当声明一个 count 变量的时候,它会创建在主内存中,然后每个线程都将 count 复制一个变量副本到自己独享的工作内存中,JMM规定每个线程的工作内存是独享的,都只能操作自己工作内存中的这个 count 变量副本,操作完之后再写回主内存中,不允许线程访问主内存,也不允许线程访问其他线程的工作内存;
- public class VolatileTest {
- public int count;
- public void add() {
- count ++;
- }
-
- private static class Count extends Thread {
- VolatileTest volatileTest;
- public Count(VolatileTest volatileTest) {
- this.volatileTest = volatileTest;
- }
- @Override
- public void run() {
- super.run();
- for (int i = 0; i < 10000; i++) {
- volatileTest.add();
- }
- }
- }
- public static void main(String[] args) throws InterruptedException {
- VolatileTest volatileTest = new VolatileTest();
- Count count1 = new Count(volatileTest);
- Count count2 = new Count(volatileTest);
- count2.start();
- count1.start();
- Thread.sleep(50);
- System.out.println(volatileTest.count);
- }
- }
打印结果并不是期望的 20000;
原因:当我们有两个线程执行 count = count + 1 的操作的时候,线程 A 将 count 从主内存读到自己的工作内存中,线程 B 也将 count 从主内存读到自己的工作内存中,分别在自己的工作内存中进行 +1 操作,操作完成之后,线程 A 和 线程 B 都要将结果写回到主内存中,理想的结果是 2,但是出现结果为 1 的现象,这就产生了线程不安全的问题;
所以 JMM 模型会牵扯到开发中常见的两个问题:可见性、原子性;
可见性:线程 A 改了 count 的值,线程 B 也改了 count 的值,但是线程 A 和线程 B 之间是互相看不到对方对 count 的修改,这就是线程A和线程B 所存在的可见性问题;
如何解决可见性问题:volatile 关键字;
volatile 关键字有三个作用:
但是针对的是:一个线程写,多个线程读的时候 volatile 才没有问题;
我们给 count 加上 volatile 关键字,继续执行;
public volatile int count;
当我们使用 volatile 关键字之后,结果仍然不是我们想要的结果,这就是所说的原子性问题;
volatile 只是强迫从内存中读了以及算完之后强迫写回内存,但是我们的计算过程(count++)并不是一次就能搞定的;
原子性:线程 B 在执行 count++ 的时候,由于这个操作(count++)不是原子操作,那么这个过程是可以被打断的(比如上下文切换),当 B 被打断的时候,A 可能又继续执行了,当 A 将新的数据写回主内存的时候,B 继续执行 count++ 操作,当执行完写回主内存的时候,就发生了数据异常,因为操作不是原子操作,就存在着被打断的可能,这就是原子性问题;
如何解决原子性问题:加锁;
也就是说:synchronized 同时保证了原子性和可见性,而 volatile 可以说是 JDK 提供的最最轻量级的同步机制,只能保证它的可见性,保证不了它的原子性;
现代 CPU 中有各种高速缓存,CPU 需要把数据读到高速缓存中,就会有一批数据在这个高速缓存区,那么现代 CPU 就引入了这个操作流水线和指令重排序,换句话说,CPU并不像我们想象的那样一次只能执行一条指令,它可以同时一次执行多条指令,例如:
而且 CPU 还可能提前就把 volatileTest.count 进行了获取,并把它放到重排序缓存区,等到真正执行到System.out.println() 的时候,再把它从重排序缓存区中取出放到指定的指令位置;
这就是操作流水线和指令重排序;在单线程上不管是CPU还是JVM,无论它怎么重排序,结果一定是符合我们的要求的,CPU和JMM都进行了保证;但是在多线程上,这种重排序就可能造成一种混乱的现象;
所以 volatile 还有一个功能就是:抑制重排序;
有 volatile 关键字修饰的变量进行写操作的时候会使用CPU提供的Lock前缀指令;
独占锁也是显示锁,而显示锁我们有在 如何应对Android面试官->线程和进程,手写ThreadLocal 中讲解,显示锁都实现了 Lock 接口,所以我们自定义的独占锁需要实现 Lock 接口;
- /**
- * 线程、进程手写ThreadLocal章节有介绍显示锁都是实现的 Lock 接口
- * */
- public class CustomReetrantLock implements Lock {
- /**
- * 仅需要将操作代理到 sync 上即可
- * */
- private final Sync sync = new Sync();
-
- private static class Sync extends AbstractQueuedSynchronizer {
-
- /**
- * 判断锁是否处于占用状态
- * */
- @Override
- protected boolean isHeldExclusively() {
- return getState() == 1;
- }
-
- /**
- * 获取锁
- * */
- @Override
- protected boolean tryAcquire(int arg) {
- // 原子交换,将0修改为1,抢占锁
- if (compareAndSetState(0, 1)) {
- // 告诉其他线程,当前线程抢到了锁
- setExclusiveOwnerThread(Thread.currentThread());
- return true;
- } else if (getExclusiveOwnerThread() == Thread.currentThread()) {
- setState(getState() + 1);
- }
- return false;
- }
-
- /**
- * 释放锁
- * */
- @Override
- protected boolean tryRelease(int arg) {
- if (getExclusiveOwnerThread() != Thread.currentThread()) {
- throw new IllegalMonitorStateException();
- }
-
- if (getState() == 0) {
- throw new IllegalMonitorStateException();
- }
-
- setState(getState() - 1);
- if (getState() == 0) {
- setExclusiveOwnerThread(null);
- }
- return true;
- }
- Condition newCondition() {
- return new ConditionObject();
- }
- }
-
- /**
- * 锁的拿取,代理到 Sync 上
- * */
- @Override
- public void lock() {
- sync.acquire(1);
- }
-
- @Override
- public void lockInterruptibly() throws InterruptedException {
- sync.acquireInterruptibly(1);
- }
-
- @Override
- public boolean tryLock() {
- return sync.tryAcquire(1);
- }
-
- @Override
- public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
- return sync.tryAcquireNanos(1, unit.toNanos(time));
- }
-
- /**
- * 锁的释放,代理到 Sync 上
- * */
- @Override
- public void unlock() {
- sync.release(1);
- }
-
- @Override
- public Condition newCondition() {
- return sync.newCondition();
- }
-
- public boolean isLocked() {
- return sync.isHeldExclusively();
- }
-
- public boolean hasQueuedThreads() {
- return sync.hasQueuedThreads();
- }
- }
简历上可写:深度理解 AQS 原理和 volatile 关键字,可手写 ReentrantLock 核心实现;
带你玩转 synchronized 关键字;
来都来了,点个关注、点个赞吧~~ 你的支持是我最大的动力
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。