线程的理解

线程中使用的并发包

 

  1. 提供了比 synchronized 更加高级的各种同步结构,包括 CountDownLatch、CyclicBarrier、Semaphore 等,可以实现更加丰富的多线程操作,
    比如利用 Semaphore 作为资源控制器,限制同时进行工作的线程数量。
  2. 各种线程安全的容器,比如最常见的 ConcurrentHashMap、有序的 ConcurrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组 CopyOnWriteArrayList 等。
  3. 各种并发队列实现,如各种 BlockingQueue 实现,比较典型的 ArrayBlockingQueue、 SynchronousQueue 或针对特定场景的 PriorityBlockingQueue 等。
  4. 强大的 Executor 框架,可以创建各种不同类型的线程池,调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。

 

思考的问题:

你使用过类似 CountDownLatch 的同步结构解决实际问题吗?谈谈你的使用场景和心得。
回答:
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

 

理解:  

参考的地址: https://blog.csdn.net/wl_ang/article/details/104922464

CountDownLatch 的计数器是大于或等于线程数的,而CyclicBarrier是一定等于线程数

CountDownLatch

说明: 一个线程等待其他线程执行完之后再执行,相当于加强版的join,在初始化CountDownLatch是需要设定计数器的数值(计数器数据不一定跟线程数相同,但是一定计数器的值一定是要大于等于线程数,一个线程中可以进行多次扣减。当计数器扣减至0时才可继续向下执行)

举例说明:
比如LOL在游戏开始时需要玩家全部准备完毕之后才开始,开始游戏可以理解为“主线程”,玩家准备理解为“其他线程”,在“其他线程”没有准备完毕之前,“主线程时等待状态”,当“其他线程”准备完毕之后“主线程”就会执行下一步开始游戏的动作

public class CountDownLatchTest {
private static CountDownLatch countDownLatsh = new CountDownLatch(5);

private
static class Player implements Runnable{ private Integer index; public Player(Integer index){ this.index = index; } @Override public void run() { System.out.println("玩家"+index+"准备完成"); countDownLatsh.countDown(); } } public static void main(String[] args) throws InterruptedException { for(int i = 0; i < 5; i++){ Player player = new Player(i); Thread thread = new Thread(player); thread.start(); } countDownLatsh.await(); System.out.println("玩家准备完毕,开始游戏"); }

-------------------------------------- 运行结果--------------------------------------

玩家0准备完成
玩家1准备完成
玩家2准备完成
玩家3准备完成
玩家4准备完成
玩家准备完毕,开始游戏

 

cyclicbarrier
说明: 让一组线程到达某个屏障,然后被阻塞,一直到最后一个线程到达屏障,然后屏障开放,所有被阻塞的线程继续执行,计数器与线程数相等。
CyclicBarrier(int parties, Runnable barrierAction) 当时使用这个构造函数时,barrierAction定义的任务会执行

举例说明: 假设有一家公司要全体员工进行团建活动,活动内容为翻越三个障碍物,每一个人翻越障碍物所用的时间是不一样的。但是公司要求所有人在翻越当前障碍物之后再开始翻越下一个障碍物,也就是所有人翻越第一个障碍物之后,才开始翻越第二个,以此类推比如跨栏比赛,我们修改一下规则,当所有选手都跨过第一个栏杆是,才去跨第二个,以此类推,每一个员工都是一个“其他线程”。当所有人都翻越的所有的障碍物之后,程序才结束。而主线程可能早就结束了,这里我们不用管主线程。

public class CyclicBarrierTest {
private
static CyclicBarrier cyclicBarrier = new CyclicBarrier(5); public static class Surmount implements Runnable{ @Override public void run() { try { for(int i = 1; i < 4; i++){ Random rand = new Random(); int randomNum = rand.nextInt((3000 - 1000) + 1) + 1000;//产生1000到3000之间的随机整数 Thread.sleep(randomNum); String name = Thread.currentThread().getName(); System.out.println(name+"翻过了第" + i +"个障碍"); cyclicBarrier.await(); } } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } public static void main(String[] args){ for (int i = 1; i < 6; i++){ Thread thread = new Thread(new Surmount(),"选手"+ i ); thread.start(); } System.out.println("main is end"); }


----------------------------------运行结果-------------------------

main is end
选手4翻过了第1个障碍
选手2翻过了第1个障碍
选手1翻过了第1个障碍
选手5翻过了第1个障碍
选手3翻过了第1个障碍
选手1翻过了第2个障碍
选手4翻过了第2个障碍
选手3翻过了第2个障碍
选手5翻过了第2个障碍
选手2翻过了第2个障碍
选手5翻过了第3个障碍
选手2翻过了第3个障碍
选手1翻过了第3个障碍
选手4翻过了第3个障碍
选手3翻过了第3个障碍
————————————————

 

 

 

 

线程的内存存放的位置:

  创建的线程 分配的内存空间就是到堆内存中,当线程使用对象是全部都去堆内存中取数据。

线程池的四中拒绝策略:

  在线程中定义要实现的ThreadPoolExecutor 类, ThreadPoolExecutor中 默认的 拒绝策略是  AbortPolicy

corePoolSize 线程池核心线程数量,
maximumPoolSize 线程池允许的最大线程数量,
keepAliveTime 空闲线程的存活时间,
unit 时间单位,
workQueue 等待队列,
threadFactory 线程工厂,
handler 拒绝处理器。
RejectedExecutionHandler 是一个接口,定义了线程池拒绝线程需要执行的方法。

public
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {

 

第二种:

CallerRunsPolicy,顾名思义,就是调用者执行,指的是主线程提交一个任务给线程池,线程池很忙拒绝了,如果线程池采用的是此种策略,则交由主线程自己去完成。

public
static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } oid rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }

第三种:

DiscardPolicy,直接丢弃,不做任何处理

public
static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }

第四种:

DiscardOldestPolicy,这种策略指的是丢弃等待队列里头节点的任务,执行刚提交的任务。
线程池拒绝的时机,我们可以看一下execute方法

public
static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }


public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }

主要分3步:

  1.如果线程池的工作线程小于设置的核心线程数,则调用addWorker增加一个工作线程,Worker为ThreadPoolExecutor定义的工作线程,继承于AbstractQueuedSynchronizer(implementing blocking locks);

  2.如果线程可以成功加入等待队列,则做一次double-check,即加入了队列后再次检查线程池运行状态,如果状态异常则从队列里删除,执行拒绝策略,否则如果判断没有工作线程了,则新加一个工作线程;

  3.如果等待队列已经满了,则尝试新加一个工作线程,如果失败执行拒绝策略。


这里简单从源码角度分析了线程池默认实现的四种拒绝策略,当然我们也可以通过实现RejectedExecutionHandler接口自定义拒绝策略处理器,然后在定义线程池的时候指定一下就可以了。

 

拒绝策略场景分析

  AbortPolicy

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

A handler for rejected tasks that throws a {@code RejectedExecutionException}.

这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现。

 

DiscardPolicy

  

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。

A handler for rejected tasks that silently discards therejected task.

 使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,本人的博客网站统计阅读量就是采用的这种拒绝策略。

 

DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.

此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。

 

CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.

如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务,我们可以通过代码来验证这一点:

 

posted @ 2020-10-28 17:28  那一的眸相遇  阅读(122)  评论(0编辑  收藏  举报