2018最新Java面试题整理 联系客服

发布时间 : 星期五 文章2018最新Java面试题整理更新完毕开始阅读807c89640640be1e650e52ea551810a6f524c8b5

同样是首先尝试释放锁,具体实现在CountDownLatch中:

死循环加上cas的方式保证state的减1操作,当计数值等于0,代表所有子线程都执行完毕,被await阻塞的线程可以唤醒了,下一步调用doReleaseShared:

标记1里,头节点状态如果SIGNAL,则状态重置为0,并调用unparkSuccessor唤醒下个节点。

标记2里,被唤醒的节点状态会重置成0,在下一次循环中被设置成PROPAGATE状态,代表状态要向后传播。

参考:

分析CountDownLatch的实现原理 什么时候使用CountDownLatch

Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

1) CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:

CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行; 而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。 2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

1.3.4 说说 CyclicBarrier 原理

参考:

JUC回顾之-CyclicBarrier底层实现和原理

1.3.5 说说 Semaphore 原理

JAVA多线程–信号量(Semaphore)

JUC回顾之-Semaphore底层实现和原理

1.3.6 说说 Exchanger 原理

java.util.concurrent.Exchanger应用范例与原理浅析

1.3.7 说说 CountDownLatch 与 CyclicBarrier 区别

CountDownLatch 减计数方式

计算为0时释放所有等待的线程 计数为0时,无法重置

调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 不可重复利用

CyclicBarrier 加计数方式

计数达到指定值时释放所有等待线程 计数达到指定值时,计数置为0重新开始 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 可重复利用

尽量把CyclicBarrier和CountDownLatch的区别说通俗点

1.3.8 ThreadLocal 原理分析

ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:

1) 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持

到其中,各管各的,线程可以正确的访问到自己的对象。

2) 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程

的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

Java并发编程:深入剖析ThreadLocal

1.3.9 讲讲线程池的实现原理

线程池的具体实现原理,将从下面几个方面讲解: 1. 线程池状态

1) 当创建线程池后,初始时,线程池处于RUNNING状态;

2) 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能

够接受新的任务,它会等待所有任务执行完毕; 3) 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接

受新的任务,并且会去尝试终止正在执行的任务;

4) 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存

队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

2. 任务的执行

1) 首先,要清楚corePoolSize和maximumPoolSize的含义; 2) 其次,要知道Worker是用来起到什么作用的;

3) 要知道任务提交给线程池之后的处理策略,这里总结一下主要有4点:

? 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创

建一个线程去执行这个任务;

? 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其

添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;

? 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略

进行处理;

? 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过

keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

3. 线程池中的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

? prestartCoreThread():初始化一个核心线程; ? prestartAllCoreThreads():初始化所有核心线程

注意上面传进去的参数是null,如果传进去的参数为null,则最后执行线程会阻塞在getTask方法中的workQueue.take();即等待任务队列中有任务。 4. 任务缓存队列及排队策略

在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。workQueue的类型为BlockingQueue,通常可以取下面三种类型: ? ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小; ? LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队

列大小,则默认为Integer.MAX_VALUE;

? synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接

新建一个线程来执行新来的任务。

5. 任务拒绝策略