java 并发HelloWorld之AQS HelloWorld

今天聊一聊什么是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多了呢,是不是更看不懂了呢?????

The End

好吧这真的只是AQS的HelloWorld各位大佬轻喷啊,本文章只是作者自己的学习笔记,仅供参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值