目录:
- 第一题 谈谈你对AQS的理解,AQS如何实现可重⼊锁?
- 第二题. Sychronized的偏向锁、轻量级锁、重量级锁
- 第三题 CountDownLatch和Semaphore的区别和底层原理
- 第四题 线程池中阻塞队列的作⽤?为什么是先添加列队⽽不是先创建最⼤线程?
- 第五题 对守护线程的理解
第一题 谈谈你对AQS的理解,AQS如何实现可重⼊锁?
- AQS是⼀个JAVA线程同步的框架。是JDK中很多锁⼯具的核⼼实现框架。
- 在AQS中,维护了⼀个信号量state和⼀个线程组成的双向链表队列。其中,这个线程队列,就是⽤来给线程排队的,⽽state就像是⼀个红绿灯,⽤来控制线程排队或者放⾏的。 在不同的场景下,有不⽤的意义。
- 在可重⼊锁这个场景下,state就⽤来表示加锁的次数。0标识⽆锁,每加⼀次锁,state就加1。释放锁state就减1。
第二题. Sychronized的偏向锁、轻量级锁、重量级锁
- 偏向锁:在锁对象的对象头中记录⼀下当前获取到该锁的线程ID,该线程下次如果⼜来获取该锁就可以直接获取到了
- 轻量级锁:由偏向锁升级⽽来,当⼀个线程获取到锁后,此时这把锁是偏向锁,此时如果有第⼆个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过⾃旋来实现的,并不会阻塞线程
- 如果⾃旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞
- ⾃旋锁:⾃旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就⽆所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进⾏的,⽐较消耗时间,⾃旋锁是线程通过CAS获取预期的⼀个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程⼀直在运⾏中,相对⽽⾔没有使⽤太多的操作系统资源,⽐较轻量。
第三题 CountDownLatch和Semaphore的区别和底层原理
CountDownLatch表示计数器,可以给CountDownLatch设置⼀个数字,⼀个线程调⽤CountDownLatch的await()将会阻塞,其他线程可以调⽤CountDownLatch的countDown()⽅法来对CountDownLatch中的数字减⼀,当数字被减成0后,所有await的线程都将被唤醒。
对应的底层原理就是,调⽤await()⽅法的线程会利⽤AQS排队,⼀旦数字被减为0,则会将AQS中排队的线程依次唤醒。
Semaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使⽤该信号量,通过acquire()来获取许可,如果没有许可可⽤则线程阻塞,并通过AQS来排队,可以通过release()⽅法来释放许可,当某个线程释放了某个许可后,会从AQS中正在排队的第⼀个线程开始依次唤醒,直到没有空闲许可。
第四题 线程池中阻塞队列的作⽤?为什么是先添加列队⽽不是先创建最⼤线程?
1、⼀般的队列只能保证作为⼀个有限⻓度的缓冲区,如果超出了缓冲⻓度,就⽆法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续⼊队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进⼊wait状态,释放cpu资源。
阻塞队列⾃带阻塞和唤醒的功能,不需要额外处理,⽆任务执⾏时,线程池利⽤阻塞队列的take⽅法挂起,从⽽维持核⼼线程的存活、不⾄于⼀直占⽤cpu资源
2、在创建新线程的时候,是要获取全局锁的,这个时候其它的就得阻塞,影响了整体效率。
就好⽐⼀个企业⾥⾯有10个(core)正式⼯的名额,最多招10个正式⼯,要是任务超过正式⼯⼈数(task > core)的情况下,⼯⼚领导(线程池)不是⾸先扩招⼯⼈,还是这10⼈,但是任务可以稍微积压⼀下,即先放到队列去(代价低)。10个正式⼯慢慢⼲,迟早会⼲完的,要是任务还在继续增加,超过正式⼯的加班忍耐极限了(队列满了),就的招外包帮忙了(注意是临时⼯)要是正式⼯加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。
第五题 对守护线程的理解
守护线程:为所有⾮守护线程提供服务的线程;任何⼀个守护线程都是整个JVM中所有⾮守护线程的保姆;
守护线程类似于整个进程的⼀个默默⽆闻的⼩喽喽;它的⽣死⽆关重要,它却依赖整个进程⽽运⾏;哪天其他线程结束了,没有要执⾏的了,程序就结束了,理都没理守护线程,就把它中断了;
注意: 由于守护线程的终⽌是⾃身⽆法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;因为它不靠谱;
守护线程的作⽤是什么?
举例, GC垃圾回收线程:就是⼀个经典的守护线程,当我们的程序中不再有任何运⾏的Thread,程序就不会再产⽣垃圾,垃圾回收器也就⽆事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会⾃动离开。它始终在低级别的状态中运⾏,⽤于实时监控和管理系统中的可回收资源。
应⽤场景:(1)来为其它线程提供服务⽀持的情况;(2) 或者在任何情况下,程序结束时,这个线程必须正常且⽴刻关闭,就可以作为守护线程来使⽤;反之,如果⼀个正在执⾏某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,⽽是⽤户线程。通常都是些关键的事务,⽐⽅说,数据库录⼊或者更新,这些操作都是不能中断的。
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出⼀个IllegalThreadStateException异常。你不能把正在运⾏的常规线程设置为守护线程。
在Daemon线程中产⽣的新线程也是Daemon的。
守护线程不能⽤于去访问固有资源,⽐如读写操作或者计算逻辑。因为它会在任何时候甚⾄在⼀个操作的中间发⽣中断。
Java⾃带的多线程框架,⽐如ExecutorService,会将守护线程转换为⽤户线程,所以如果要使⽤后台线程就不能⽤Java的线程池。
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力