juc aqs 原理
一.背景
并发包中的各种并发工具基础都是用了AQS(AbstractQueuedSynchronizer),这个工具和Synchronizer关键字提供的功能类似,但是比Synchronizer要更好用
有如下改进:
1.获取锁时,AQS时能响应中断(阻塞线程被唤醒后会检查中断标志位,来响应中断);Synchronized不会响应中断(线程阻塞在Synchronized时(这是java线程状态是"Blocked"),不会响应中断(被唤醒后,不会检查线程中断标志位))。
2.获取锁时,AQS可以超时时间,超时后返回失败,不会一直阻塞线程;Synchronized获取锁时,不能指定超时时间,阻塞线程会一直阻塞下去,直到获取到锁。
3.获取锁时,AQS可以直接返回,不管是否获取到锁,不会阻塞线程;Synchronized,如果锁被其他线程获取了,当前线程只能阻塞等待。
4.AQS可以有多个条件变量,来用于线程同步,且在等待条件变量时可以设置是否响应中断、是否指定超时时间、是否不阻塞;Synchronized只有一个条件变量,来用于线程同步,等待条件变量时(object.wait()),只能一直阻塞下去,等待被其他线程唤醒或者被中断唤醒。
5.AQS可以是独占锁,也可以是共享锁;Synchronized只能是独占锁
6.AQS可以是公平锁,也可以是非公平锁;Synchronized只能是非公平锁,不能保证先来的线程先获取到锁
二.原理
1.获取锁的过程
i.获取锁时 1.1如果锁空闲,设置为已占用状态后,直接返回锁成功(非公平锁,等待队列中的线程不一定能抢到锁) 1.2直接把当前线程放到锁等待队列的末尾(公平锁),并调用park,线程进入睡眠状态 ii.释放锁时 如果锁等待队列为空,直接设置锁为未锁定状态,返回 否则找到锁等待队列第一个线程,把这个线程唤醒,这个线程唤醒后,会去争抢锁;如果争取失败,调用park,进入睡眠状态,该线程还在队列头 如果队列头第一个线程抢到了锁,会把自己从锁等待队列中摘除。
2.条件变量等待唤醒过程
1.进入等待时,把当前线程加入到等待队列,并调用park,线程进入睡眠状态。
2.唤醒某个线程时,找到等待队列中指定的线程,把这个线程唤醒,把这个线程从队列中摘除。
3.独占锁API
1 void acquire(int args);//独占式获取同步状态,如果获取失败插入同步队列进行等待 2 void acquireInterruptibly(int arg);//在1的基础上,此方法可以在同步队列中响应中断 3 boolean tryAcquireNanos(int arg,long nanosTimeOut);//在2的基础上增加了超时等待功能,到了预计时间还未获得锁直接返回 4 boolean tryAcquire(int arg); //获取锁成功返回true、失败返回false 5 boolean release(int arg); //释放同步状态、该方法会唤醒在同步队列的下一个结点
4.共享锁API
1 void acquireShared(int arg);//共享获取同步状态、同一时刻多个线程获取同步状态 2 void acquireSharedInterruptibly(int arg);//在1的基础上增加响应中断 3 boolean tryAcquireSharedNanos(int arg,long nanosTimeOut);//在2的基础上增加超时等待 4 boolean releaseShared(int arg);//共享式释放同步状态
5.等待方法API
1 void await() throws InterruptedException;//同Object的await()、直到被中断或者唤醒 2 void awaitUninterruptibly(); //不响应中断、直到被唤醒 3 boolean await(long time, TimeUnit unit) throws InterruptedException; //同Object.wait(long timeout),多了自定义时间单位 中断、超时、被唤醒 4 boolean awaitUntil(Date deadline) throws InterruptedException; //支持设置截止时间;
6.唤醒方法API
void signal(); //唤醒一个等待在Condition上的线程,将该线程由等待队列转移到同步队列中 void signalAll();//将所有等待在condition上的线程全部转移到同步队列中