AQS 的理解
第一 : AQS 是AbstractQueuedSynchronizer的简称,抽象同步队列,是并发编程比较核心的组件,是多线程
同步器,是JUC包下多个组件的底层实现,比如 Lock 闭锁 信号量等
第二 ; 本质讲,AQS提供两种锁机制,一排他,二共享锁
排他 : 多线程竞争同一个共享资源时,同一时刻只允许一个线程访问该共享资源,最后只有一个线程获得锁
资源,比如 ReetrantLock重入锁就是用到AQS的排他功能;
共享锁 : 也称读锁,同一时刻允许多线程同时获得锁资源,比如闭锁信号量都是用AQS的共享锁
第三 : AQS 底层的关键是一个state 和一个双向队列以及CAS算法实现;
每个节点代表一个线程,节点有两个属性,一是状态state,二是线程thread,前者表示该线程是否拿到锁
或等待队列有多少线程正在等待,线程属性用来记录该节点对应的线程;
双向链表存储等待队列的节点,节点都有一个前驱和后继;当一个线程想获取锁时,会建一个节点插入到等待
队列尾部,需要通过CAS保证原子性插入操作,失败的话,说明有其它线程插入成功,进行重新尝试;当一个
线程释放锁,它会修改状态并唤醒等待队列中的后继节点,也是CAS操作,因为可能存在多线程同时竞争唤醒
同一个节点的情况
Lock 和 synchronized的区别
第一方面,功能角度,两者都是java中解决线程安全问题的工具
第二方面,特性来看
a.sync是java的关键字,Lock是JUC包的接口,API,这个接口有很多实现类,其中包括ReentrantLock重入锁
b.sync可以通过两种方式控制锁粒度
// 修饰方法
public synchronized void mehhod(){
}
Object lock = new Object();
// 修饰代码块
public void method(){
synchronized(lock){
}
}
如上,一种是修饰方法,一种是修饰代码块,另外锁对象是静态对象或类对象,锁是全局锁,整个类;锁的是普通
实例对象,那么锁的范围就是该实例的生命周期;
lock锁粒度通过它里面提供的lock()和unlock()方法决定,包裹在两方法间的代码能保证线程安全;
Lock lock = new ReentrantLock();
public void method(){
lock.lock(); // 竞争锁
// 代码
lock.unlock(); // 释放锁
}
c. lock比sync灵活度更高,Lock可以决定什么时候加锁,什么时候释放锁,两个方法即可,还提供了非阻塞
的竞争锁方法tryLock(),返回值真假来告诉当前线程是否已经有其它线程正在使用锁
sync由于是关键字,无法实现非阻塞竞争锁的方法,锁的释放是被动的,必须同步代码块执行完或者异常
才会释放
d. lock提供公平锁和非公平锁机制,sync只有非公平锁实现
第三方面,性能来看,差别不大,实现有一些区别,sync引入锁升级优化,而lock用自旋锁方式实现性能优化
:::success
备注 : 上表第六行有一个错别字, Lock 是可中断类型的锁;
:::
线程池如何知道一个线程的任务已经执行完成
第一种 : 线程池内部,工作线程执行run方法,,run方法正常结束,任务执行结束;等run方法返回,可以统计任务
的完成数量;
第二种 : 如果在线程池外部去获得内部任务的执行状态,
方法一 : isTerminated()方法,可判断线程池运行状态,循环判断,一旦状态是Terminated意味任务
执行完.前提是程序主动调用线程池shutdown()方法,因此实用性灵活性不够;
方法二 ; 在线程池中,有一个 submit()方法,它提供了一个 Future 的返回值,我们通
过 Future.get()方法来获得任务的执行结果,当线程池中的任务没执行完之前,
future.get()方法会一直阻塞,直到任务执行结束。因此,只要 future.get()
方法正常返回,也就意味着传入到线程池中的任务已经执行完成了!
方法三 : 可以引入一个 CountDownLatch 计数器,它可以通过初始化指定一个计数器
进行倒计时,其中有两个方法分别是 await()阻塞线程,以及 countDown()
进行倒计时,一旦倒计时归零,所以被阻塞在 await()方法的线程都会被释放。
综上 : 不管是线程池内部还是外部,要想知道线程是否执行结束,我们必须要获取线程执行结束后的状态,而线程
本身没有返回值,所以只能通过阻塞-唤醒的方式来实现,future.get 和 CountDownLatch 都是这样原理。
阻塞队列的有界和无界
1.阻塞队列,是一种特殊队列,是在普通队列基础上提供两个附加功能:
a. 当队列为空的时候,获取队列中元素的消费者线程会被阻塞,同时唤醒生产者线程。
b. 当队列满了的时候,向队列中添加元素的生产者线程被阻塞,同时唤醒消费者线程。
2.其中,阻塞队列中能够容纳的元素个数,通常情况下是有界的,比如我们实例化一
个 ArrayBlockingList,可以在构造方法中传入一个整形的数字,表示这个基于数
组的阻塞队列中能够容纳的元素个数。这种就是有界队列。
3. 而无界队列,就是没有设置固定大小的队列,不过它并不是