开篇介绍
大家好,我是 Java 最全面试题库
的提裤姐
,明天这篇是 JavaSE 系列的第十二篇,次要总结了 Java 中的多线程问题,多线程分为三篇来讲,这篇是第二篇,在后续,会沿着第一篇开篇的常识线路始终总结上来,做到日更!如果我能做到百日百更,心愿你也能够跟着百日百刷,一百天养成一个好习惯。
启动一个线程是调用 run() 办法还是 start() 办法?
启动一个线程是调用 start()
办法,使线程所代表的虚构处理机处于可运行状态,这意味着它能够由 JVM 调度并执行,这并不意味着线程就会立刻运行。run()
办法是线程启动后要进行回调(callback)的办法。
什么状况下导致线程死锁,遇到线程死锁该怎么解决?
死锁的定义:
死锁是指多个线程因竞争资源而造成的一种相互期待状态,若无外力作用,这些过程都将无奈向前推动。
死锁的条件:
互斥条件:线程要求对所调配的资源(如打印机)进行排他性管制,即在一段时间内某 资源仅为一个线程
所占有。此时若有其余线程申请该资源,则申请线程只能期待。
不剥夺条件:线程所取得的资源在未应用结束之前,不能被其余线程强行夺走,即只能由取得该资源的线程本人来开释(只能是被动开释)。
申请和放弃条件:线程曾经放弃了至多一个资源,但又提出了新的资源申请,而该资源已被其余线程占有,此时申请过程被阻塞,但对本人已取得的资源放弃不放。
循环期待条件:存在一种线程资源的循环期待链,链中每一个线程已取得的资源同时被链中下一个线程所申请。即存在一个处于期待状态的线程汇合 {Pl, P2, …, pn},其中 Pi 期待的资源被 P(i+1) 占有(i=0, 1, …, n-1),Pn 期待的资源被 P0 占有,如图所示:
防止死锁:
①加锁程序(线程依照肯定的程序加锁)
②加锁时限(线程尝试获取锁的时候加上肯定的时限,超过时限则放弃对该锁的申请,并开释本人占有的锁)
什么是乐观锁和乐观锁?
乐观锁
Java 在 JDK1.5 之前都是靠synchronized
关键字保障同步的,这种通过应用统一的锁定协定来协调对共享状态的拜访,能够确保无论哪个线程持有共享变量的锁,都采纳独占的形式来拜访这些变量。独占锁其实就是一种乐观锁,所以能够说synchronized 是乐观锁
。
乐观锁 乐观锁(Optimistic Locking)其实是一种思维
。绝对乐观锁而言,乐观锁假如认为数据个别状况下不会造成抵触,所以在数据进行提交更新的时候,才会正式对数据的抵触与否进行检测,如果发现抵触了,则让返回用户谬误的信息,让用户决定如何去做。
乐观锁肯定就是好的吗?
乐观锁防止了乐观锁独占对象的景象,同时也进步了并发性能,但它也有毛病:
1.乐观锁只能保障一个共享变量的原子操作
。如果多一个或几个变量,乐观锁将变得力不从心,
但互斥锁能轻易解决, 不论对象数量多少及对象颗粒度大小。
2.长时间自旋可能导致开销大
。如果 CAS 长时间不胜利而始终自旋, 会给 CPU 带来很大的开销。
3.ABA 问题
。CAS 的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不谨严, 如果内存值原来是 A,起初被一条线程改为 B,最初又被改成了 A,则 CAS 认为此内存值并没有产生扭转,但实际上是有被其余线程改过的,这种状况对依赖过程值的情景的运算后果影响很大。
解决的思路是引入版本号, 每次变量更新都把版本号加一。
说一下线程池的启动策略?
线程池的执行过程形容:
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
1、线程池刚创立时,外面没有一个线程。工作队列是作为参数传进来的。不过,就算队列外面有工作,线程池也不会马上执行它们。
2、当调用 execute()
办法增加一个工作时,线程池会做如下判断:
- ①如果正在运行的线程数量小于
corePoolSize
,那么马上创立线程运行这个工作; - ②如果正在运行的线程数量大于或等于
corePoolSize
,那么将这个工作放入队列。 - ③如果这时候队列满了,而且正在运行的线程数量小于
maximumPoolSize
,那么还是要创立线程运行这
个工作;
- ④如果队列满了,而且正在运行的线程数量大于或等于
maximumPoolSize
,那么线程池会抛出异样,告
诉调用者“我不能再接受任务了”。
3、当一个线程实现工作时,它会从队列中取下一个工作来执行。
4、当一个线程无事可做,超过肯定的工夫(keepAliveTime
)时,线程池会判断,如果以后运行的线程数大于corePoolSize
,那么这个线程就被停掉。所以线程池的所有工作实现后,它最终会膨胀到 corePoolSize
的大小。
线程池中的线程是怎么创立的? 是一开始就随着线程池的启动创立好的吗?
不是。线程池默认初始化后不启动 Worker, 期待有申请时才启动。
当调用 execute 办法增加一个工作时, 线程池会做如下判断:
- 如果正在运行的线程数量小于
corePoolSize
, 那么马上创立线程运行这个工作; - 如果正在运行的线程数量大于或等于
corePoolSize
, 那么将这个工作放入队列; - 如果这时候队列满了, 而且正在运行的线程数量小于
maximumPoolSize
, 那么还是要创立非核心线程立即运行这个工作; - 如果队列满了, 而且正在运行的线程数量大于或等于
maximumPoolSize
, 那么线程池会抛出异样RejectExecutionException
当一个线程实现工作时, 它会从队列中取下一个工作来执行。
当一个线程无事可做, 超过肯定的工夫 (keepAlive) 时, 线程池会判断。
如果以后运行的线程数大于 corePoolSize, 那么这个线程就被停掉。所以线程池的所有工作实现后, 它最终会膨胀到 corePoolSize 的大小。
请说出同步线程及线程调度相干的办法?
wait()
:使一个线程处于期待(阻塞)状态,并且开释所持有的对象的锁;sleep()
:使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此办法要解决 InterruptedException 异样;notify()
:唤醒一个处于期待状态的线程,当然在调用此办法的时候,并不能确切的唤醒某一个期待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;notityAll()
:唤醒所有处于期待状态的线程,该办法并不是将对象的锁给所有线程,而是让它们竞争,只有取得锁
的线程能力进入就绪状态;
留神:java 5 通过 Lock 接口提供了显示的锁机制,Lock 接口中定义了加锁(lock()办法)和解锁(unLock()办法),加强了多线程编程的灵活性及对线程的协调