今天聊一聊什么是AQS,并用他实现一个简单的锁。
它是java.util.concurrent.locks.AbstractQueuedSynchronizer
是一个抽象类,juc很多工具都是基于它的,它是juc的核心
它实现的是非系统级别的锁的一个框架有别于synchronized关键词,系统级别的锁(synchronized)对于锁的竞争切换的时候消耗资源比较大因为要调用系统级别的api还要切换用户态到啥啥啥的,然后又切回来,以前一直说synchronized性能差,在JDK1.6之后,对synchronized一定量的优化,这里就不一一展开了。
使用AQS的锁是不调用系统级别的api的,它里面自己实现了等待队列等一些列无敌骚的操作,在某些场景下表现会比synchronized的性能好
jdk文档的解释:
提供一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量,事件等)。 该类被设计为大多数类型的同步器的有用依据,这些同步器依赖于单个原子int值来表示状态。 子类必须定义改变此状态的受保护方法,以及根据该对象被获取或释放来定义该状态的含义。 给定这些,这个类中的其他方法执行所有排队和阻塞机制。 子类可以保持其他状态字段,但只以原子方式更新int使用方法操纵值getState() , setState(int)和compareAndSetState(int, int)被跟踪相对于同步。
子类应定义为非公共内部助手类,用于实现其封闭类的同步属性。 AbstractQueuedSynchronizer类不实现任何同步接口。 相反,它定义了一些方法,如acquireInterruptibly(int) ,可以通过具体的锁和相关同步器来调用适当履行其公共方法。
此类支持默认独占模式和共享模式。 当以独占模式获取时,尝试通过其他线程获取不能成功。 多线程获取的共享模式可能(但不需要)成功。 除了在机械意义上,这个类不理解这些差异,当共享模式获取成功时,下一个等待线程(如果存在)也必须确定它是否也可以获取。 在不同模式下等待的线程共享相同的FIFO队列。 通常,实现子类只支持这些模式之一,但是两者都可以在ReadWriteLock中发挥作用 。 仅支持独占或仅共享模式的子类不需要定义支持未使用模式的方法。
自己实现一个简单的锁
直接上代码
//无意义只是演示java.util.concurrent.locks.Lock; 怎么用
public class MyLock2 implements Lock {
private int value= 0;
@Override
public void lock() {
synchronized (this){
while (value == 1){ //已经有线程占用
try {
this.wait(); //阻塞 CAS 一直在自旋
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value = 1; //获得锁
}
}
@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() {
synchronized (this){
//锁开了
value = 0;
//唤醒当前对象等待池里面所有等待的线程
this.notifyAll();
}
}
@Override
public Condition newCondition() {
return null;
}
}
我们的显示锁都实现了java.util.concurrent.locks.Lock这个接口,这里演示一下它的简单使用。这个锁虽然没有意义(synchronized还是系统级别的锁),但是它演示了这个接口怎么使用,为下面的学习打下了基础。
解释:当调用lock去获取锁的时候,会判断当前线程有没有锁,如果没有获取到,就会进入等待池,下次unlock()方法的调用,unlock()会把 value = 0;然后唤醒等待池所有的线程让他们开始争抢如果谁抢到了它的value就等于一,其他线程继续wait等待下次unlock()方法的调用
下面是测试代码
开100个线程每个线程都把调用100次value++
public class TestLock1 {
private static int value = 0;
private static int count = 100;
private static Lock lock = new MyLock2();
private static Thread[] threads = new Thread[count];
public static void main(String[] args) throws InterruptedException {
//init
for (int i = 0; i <threads.length ; i++) {
threads[i] = new Thread(()->{
try {
lock.lock();
for (int j = 0; j < 100 ; j++) {
value++;
}
} finally {
lock.unlock();
}
});
}
//run
for (int i = 0; i <threads.length ; i++) {
threads[i].start();
}
//等下线程全部执行完
for (int i = 0; i <threads.length ; i++) {
threads[i].join();
}
System.out.println(value);
}
}
使用AQS版本
public class MyLock implements Lock {
private Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@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() {
sync.release(1);
}
@Override
public Condition newCondition() {
return null;
}
/**
* @Description:
* @Author: guo
* @CreateDate: 2019/6/9
* @UpdateUser:
*/
private class Sync extends AbstractQueuedSynchronizer{
//使用CAS设置同步器的状态 期望值如果是0 到 新值 1
//CAS其实就是乐观锁
//CAS比较和交换的过程必须是原子操作,比较和交换的时候其他线程不能打断他
//CAS使用的是native方法调用了现代CPU里面实现的CAS
//CAS判断现在值是不是0,如果不是0就说明另外线程把这个值已经改变了,里面有个死循环会不断的检查(自旋),
//CAS判断现在值是不是0,如果不是0就说明另外线程把这个值已经改变了
//如果是0那就把值改为1,就说明当前线程占有了这把锁
//CAS坏处:自旋会一直消耗cpu资源,高并发环境下竞争高会疯狂消耗cpu,建议锁竞争不高的时候用cas
//好处:不会调用操作系统级别的锁,synchronized(){}是操作系统级别的
//如果获取锁失败返回了false说明竞争不到,就会,加入到一个叫CLH的等待队列里面,排队去拿,排队也没拿到就中断当前线程
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁
@Override
protected boolean tryRelease(int arg) {
//如果当前线程没有持有锁
if (! isHeldExclusively()){
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//当前线程是否持有锁
@Override
protected boolean isHeldExclusively() {
//获得 tryAcquire(int arg)设置过排他锁的线程 是否等于当前线程
return getExclusiveOwnerThread() == Thread.currentThread();
}
}
}
官方建议使用内部类继承AbstractQueuedSynchronizer,Sync这个类跟其他类是没有关系的。
Sync类解释:
调用sync.acquire(1);和 sync.release(1);的话,里面会自动调用tryAcquire(),和tryRelease()我们只要继承AQS然后实现这两个方法就可以了,不需要自己手动实现里面的等待池啊什么乱七八糟很骚的操作。其他解释请看代码注释
这个类的使用方法和之前使用synchronized实现的锁的使用方法是一样的。哦不还有一个可以改进的地方
可以把join换成CountDownLatch
CountDownLatch也是使用了AQS是一个常用的juc类,以前我不敢ctrl进去看,现在进去一看,我靠,居然只有这么点代码,我惊了!!!!
public class TestMyLock {
private static int value = 0;
private static int count = 100;
private static MyLock lock = new MyLock();
private static CountDownLatch countDownLatch = new CountDownLatch(count);
private static Thread[] threads = new Thread[count];
public static void main(String[] args) throws InterruptedException {
//init
for (int i = 0; i <threads.length ; i++) {
threads[i] = new Thread(()->{
try {
lock.lock();
for (int j = 0; j < 100 ; j++) {
value++;
}
} finally {
lock.unlock();
}
countDownLatch.countDown();
});
}
//run
for (int i = 0; i <threads.length ; i++) {
threads[i].start();
}
//等下线程全部执行完
countDownLatch.await();
System.out.println(value);
}
}
这样是不是比join装b多了呢,是不是更看不懂了呢?????