写点什么

高并发的核心 - AQS【哪些琐是基于 AQS 来实现的】

发布于: 2020 年 12 月 03 日

1.AQS 是什么?

AQS(AbstractQueuedSynchronizer)抽象队列同步器,是 JDK 下提供的一套实现基于 FIFO 等待队列的阻塞锁和相关同步器的一个同步框架.

基础:

  1. 线程:掌握 Java 线程

  2. 锁:明白什么是锁

  3. 高并发(可选)

在 Java 新版本 里实现锁的框架

Public class Main {    public static int m = 0;//静态成员变量        public static void main(String[] args) throws Exception{        Thread[] threads = new Thread[100];//定义一个线程数组        for(int i=0; i<threads.length; i++) {            threads[i] = new Thread(()->{                               for(int j=0; j<100; j++) m++;             });        }        for(Thread t : threads) t.start();//开始运行所有线程        for(Thread t : threads) t.join();//等待所有线程结束        System.out.println(m);    }}/*这时候运行的结果并不准确*/
/*值会小于10000*/
复制代码


点击并拖拽以移动

如果这么写,运行结果并不准确;其原因是:

如果 m=0 一直++到 100,当第一个线程 m++,另一个线程也拿过来加,当第一个线程一直加到 99 时返回,而另一个线程也加到 99 返回,这样一来 m 本来应该是+2 返回,但只加了 1 就返回 ;

那么如何解决这个问题:加锁,

加锁概念

是把锁在 Main.class 身上,锁的是对象,监听的是对象。

给其加锁后,当一个线程执行时,另一个线程也要来执行时,发现锁被占用,就会发生阻塞,直到被锁上的线程执行完。

其他线程在争这把锁,谁争到谁就执行,这样就避免了俩个线程同时执行。

2.synchronized() 同步锁

  1. 为什么使用 synchronized 在并发编程中存在线程安全问题,主要原因:1.存在共享数据 2.多线程共同操作共享数据.关键词 synchronized 可以保证在同一时刻只有一个线程可以执行某个方法或某个代码块,同时 synchronized 可以保证一个线程的变化可见.

  2. synchronized 的三种应用方式普通同步方法(实例),锁是当前实例对象,进入同步代码前要获得当前实例的锁。静态同步方法,锁是当前类的 class 对象,进入同步代码前要获得当前类对象的锁。同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

  3. synchronized 的作用确保线程互斥访问同步代码。保证共享变量的修改能够及时可见。解决重排序问题。

例:

Public class Main {    public static int m = 0;//静态成员变量        public static void main(String[] args) throws Exception{        Thread[] threads = new Thread[100];//定义一个线程数组        for(int i=0; i<threads.length; i++) {            threads[i] = new Thread(()->{                /*这里锁的是对象*/                synchronized (Main.class){                for(int j=0; j<100; j++) m++;                }            });        }        for(Thread t : threads) t.start();//开始运行所有线程        for(Thread t : threads) t.join();//等待所有线程结束        System.out.println(m);    }}
复制代码


点击并拖拽以移动

3.ReentrantLock() 重入锁

ReentrantLock 底层实现就是 AQS

ReentranLock 特性(对比 synchronized)

  • 尝试获得锁

  • 获取到锁的线程能够响应中断

例:

Public class Main {    public static int m = 0;//静态成员变量    public static Lock lock = new ReentrantLock();//这里定义一把锁    public static void main(String[] args) throws Exception{        Thread[] threads = new Thread[100];//定义一个线程数组        for(int i=0; i<threads.length; i++) {            threads[i] = new Thread(()->{             /*必须要用try finally*/                try{                 lock.lock();//上锁                    for(int j=0; j<100; j++) m++;             } finally{                 lock.unlock();//解锁             }                                        });        }        for(Thread t : threads) t.start();//开始运行所有线程        for(Thread t : threads) t.join();//等待所有线程结束        System.out.println(m);    }}
复制代码


点击并拖拽以移动

4.CountDownLatch

CountDownLatch 是 Java 中自带的锁,它的实现也是基于 AQS

import java.util.concurrent.CountDownLatch;import java.util.concurrent.locks.Lock;
/** * java自身带的锁:CountDownLatch */
public class CountDown {
public static int m = 0; public static Lock lock =new Mylock(); public static CountDownLatch latch = new CountDownLatch(100);//给其一个值,为100,当所有线程结束值为零
public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[100]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(()->{ try { lock.lock(); for (int j = 0; j < 100; j++) m++;

}finally { lock.unlock(); } latch.countDown();//每次调用减一 }); } for (Thread t: threads) t.start();
latch.await();//等待latch打开,当值变为0时打开,当所有线程执行完后值变为0
System.out.println(m); }
}

复制代码


点击并拖拽以移动

5.使用 AQS 自定义锁

AQS 的使用:

在 AQS 内部有一个 int 类型的值:State,在这个数基础之上管理着一个双向队列,当有新的线程去访问这个值的时候,如果没有竞争不到访问权,就在加在这个队列之后

使用 Lock 接口来写实现类,使用 AQS 来实现锁

import java.util.concurrent.locks.AbstractQueuedSynchronizer;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.AbstractQueuedSynchronizer;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;
public class Mylock implements Lock{
private Sync sync = new Sync();
@Override public void lock() { sync.acquire(1);//调用acquire,不是当下写的Tryacquire }
@Override public void lockInterruptibly() throws InterruptedException {
}
@Override public boolean tryLock() { return false; }
@Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return false; }
@Override public void unlock() {
}
@Override public Condition newCondition() { return null; }
/*类名Sync,继承AbstractQueuedSynchronizer*/ public class Sync extends AbstractQueuedSynchronizer{ @Override /*重写 tryAcqurie方法 */ protected boolean tryAcquire(int arg) { assert arg == 1; //在JDKAPI文档中,要求要传进来一个1,所以用assert〔〕 if (compareAndSetState(0,1)) {//这里使用到了CAS /*CAS:判断传进去的值,如果是0将其变为1*/ //判断m的值,如果是零将其变为1 setExclusiveOwnerThread(Thread.currentThread());//互斥锁 return true; } return false; } @Override /*释放*/ protected boolean tryRelease(int arg) { assert arg == 1; /*判断当前线程是否持有这把锁*/ if(!isHeldExclusively()) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); return true; } @Override protected boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread();//拿到当前拥有的排它的线程和当前线程是否一样
} }
}
复制代码


点击并拖拽以移动

这里所介绍的锁都是基于 AQS 来实现.

推荐阅读

字节跳动总结的设计模式 PDF 火了,完整版开放分享

刷Github时发现了一本阿里大神的算法笔记!标星70.5K

月薪没有达到 30K 的Java程序员 是听不懂这个项目的

为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了


关于【暴力递归算法】你所不知道的思路


看完三件事❤️

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

关注公众号 『 Java 斗帝 』,不定期分享原创知识。

同时可以期待后续文章 ing🚀



用户头像

还未添加个人签名 2020.09.07 加入

还未添加个人简介

评论

发布
暂无评论
高并发的核心 - AQS【哪些琐是基于AQS来实现的】