Java中的AQS AbstractQueuedSynchronizer

599 阅读4分钟

AbstractQueuedSynchronizer

什么是AQS

AbstractQueuedSynchronizer是 java同步中基础类 是用来实现同步的

但是为什么我们在平时的使用中 却感觉从来没有用到过这个类呢?

这个是因为同步工具类中的内部类 继承AQS 这个内部把AQS封装起来 让我们察觉不到这个类的存在

AQS中使用模板方法来实现同步机制 在Android中的View的onDraw onLayout

自定义同步工具类

如果我们要实现独占 我们需要实现 tryAcquire方法

如果我们要实现共享 我们需要实现 tryAcquireShared方法

实现同步工具类关键是state这个变量 缺省都是0

显示锁都是继承Lock

那么我们要实现 显示独占锁 就继承Lock

那么lock和unlock 要如何实现锁呢 这个时候就要借用AQS的

独占 不可重入的锁

public class SelfLock implements Lock {
    //内部类继承AQS
    private static class Sync extends AbstractQueuedSynchronizer {
        /*获得锁*/
        @Override
        protected boolean tryAcquire(int arg) {
            //通过设置期望state是0 并且设置state为1 为true的时候就代表拿到了锁
            if (compareAndSetState(0, 1)) {
                //告诉别人我拿到这个线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /*释放锁*/
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }
    private final Sync sync=new Sync();
    @Override
    public void lock() {
        //获得锁
        sync.acquire(1);
    }
    @Override
    public void unlock() {
        sync.release(1);
    }

这样我们就是实现了 同步锁

AQS的基本思想 CLH队列锁

一个代码块中 有一把锁, 任意时刻只有一个线程能拿到这把锁 其他线程都在外面排队 所以就是没有拿到锁的线程要在外面排队。

所以把排队的线程打包成QNode对象

QNode:

  • 前驱节点myPred
  • 当前线程
  • locked 需要去锁的时候为true 把排队的线程 QNode形成链表

例如: 假如线程A已经排队拿锁了,那么线程B要去拿锁就会在 将自己的myPred指向 A的QNode节点(并且locked=true)

那么线程是如何拿到锁的呢?

是这样因为 拥有前一个节点的 所以B就不断的自旋CAS 检查locked的值 如果locked=false了,那么就代码前一个线程A释放了锁会把自己的locked设置为false B线程就可以拿到锁了。

考虑到性能 AQS采用双向链表 并不会不断自旋 会尝试自旋一定次数后,将线程挂起

wait和notify notifyall 也有 一个 等待队列 跟这个拿锁的队列一样

公平锁和非公平锁

公平锁就是 所有的线程都要走排队的流程 CLH拿锁的流程就是 公平锁

公平锁代码如下:

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            //hasQueuedPredecessors 公平锁会判断是否有队列排队如果有就排队
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

非公平锁就是 已经有队列了可以不排队直接插队拿锁就是非公平锁

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            //非公平锁,直接去拿锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

可重入锁

可重入锁其实就是 state的状态变化

 private static class RetainSync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            } else if (getExclusiveOwnerThread() == Thread.currentThread()) {
                setState(getState() + 1);
                return true;
            }
            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;
        }
    }

JAVA内存模型 JMM Java Memory Model

因为Cpu 读取内存 需要100纳秒 cpu执行一条指令需要0.6纳秒

如果执行a+b 读取a 需要100纳秒 读取b也需要100纳秒 +需要0.6纳秒 总共执行时间为200.6纳秒

CPU就引入了一个高速缓存

CPU的高速缓存

L1 L2 L3

  • 工作内存包含 CPU寄存器 CPU缓存(99%) 主内存(1%) 主内存非常少

  • 主内存包含 CPU缓存(1%) 主内存(99%)(就是电脑内存条) cpu缓存非常少

工作内存是线程独享的

比如某个线程要对 int count 变量进行累加 count 存储在主内存中

就会在向这个线程的工作内存创建一个count的一个副本,线程不能操作主内存中的count, 只能操作工作线程中count副本

java内存模型带来的并发问题

可以看到 count=count+1; 由于工作内存的存在 这个方法并不是安全的。 线程中的工作内存 对于同一个变量 是由可见性的问题的。 那么如何解决可见性问题
volatile 只能保证可见性 但是不能保证原子性。

volatile 可以对简单的赋值操作 但是对于复杂复合操作不能保证线程安全

volatile 可以应用在 一个线程写 多个线程读取的场景中使用