前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程周期、创建线程的方式、线程池

线程周期、创建线程的方式、线程池

作者头像
崩天的勾玉
发布2021-12-20 16:40:06
8810
发布2021-12-20 16:40:06
举报
文章被收录于专栏:崩天的勾玉崩天的勾玉

多线程也是面试必问的东西,我们要了解线程的状态周期创建线程的方式,以及线程池的使用。

线程状态周期

  1. 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
  2. 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
  3. 运行(running)状态: 执行run()方法
  4. 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
  5. 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

进入阻塞状态的原因

答:

1.等待I/O流的输入输出

2.等待网络资源,即网速问题

3.调用sleep()方法,需要等sleep时间结束

4.调用wait()方法,需要调用notify()唤醒线程

5.其他线程执行join()方法,当前线程则会阻塞,需要等其他线程执行完。

进入死亡状态的原因

答:

1.线程正常完成工作

2.调用stop()方法,强行停止线程

3.外部原因中断线程

interrupt方法用于中断线程。调用该方法的线程的状态为将被置为"中断"状态。

线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。

实现线程的四种方式

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口,重写run方法,
  3. 实现Callable接口再由callable对象创建一个FutureTask对象,由FutureTask创建一个Thread对象,再start
  4. 通过线程池ThreadPoolExecutor创建线程

为了方便管理线程和线程复用,可以使用线程池的方式。

线程池

7个参数

1、corepoolsize:核心线程数,即使空闲也不会被销毁。

2、maximumpoolsize:最大线程数,最多创建线程的数目。

3、keepalivetime:空闲存活时间,某一线程处于空闲状态并且当前线程超过核心线程数,那么就会在指定时间后被销毁。

4、unit:空闲线程存活时间单位,keepAliveTime的计量单位

5、workQueue :工作队列,任务提交后进入的队列,有四种:ArrayBlockingQueue(有界阻塞队列,先进先出;有界可以防止资源耗尽)、LinkedBlockingQuene(无界阻塞队列,先进先出,一直存入,直到线程数达到最大则拒绝新任务)、SynchronousQuene(不缓存任务,新来的任务直接创建线程被调度)、PriorityBlockingQueue(优先级无界阻塞队列,通过参数Comparator指定优先级)

6、threadFactory :线程工厂,创建一个新线程使用的工厂,可以指定线程的名字,是否为守护线程等等。

7、handler :拒绝策略,队列中任务数量达到限制后的拒绝策略,CallerRunsPolicy(直接执行拒绝任务的run方法),AbortPolicy(默认。直接丢弃任务,抛出RejectedExecutionException)、DiscardPolicy(直接丢弃任务,什么都不做)、DiscardOldestPolicy(抛弃最早进入的任务,尝试将这次的任务放进队列)

线程池的原理

  1. 在创建了线程池后,等待提交过来的任务请求。
  2. 当调用 execute() 方法添加一个请求任务时,线程池会做如下判断:2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;2.3 如果这时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  4. 当一个线程无事可做,超过一定的时间(keepAliveTime) 时,线程池会判断:如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
4种拒绝策略
  1. AbortPolicy:直接丢弃任务,抛出RejectedException 异常,这是默认策略
  2. CallerRunsPolicy:由调用线程处理该任务 【谁调用,谁处理】
  3. DiscardOldestPolicy:将最早进入队列的任务删除,之后再尝试加入队列
  4. DiscardPolicy:直接丢弃任务,也不抛出异常

wait和sleep的区别

sleep()是定义在Thread类中,而wait()方法是定义在Object类中的。因为 sleep 是让当前线程休眠,不涉及到对象类,也不需要获得对象的锁,所以是线程类的方法。wait 是让获得对象锁的线程实现等待,前提是要楚获得对象的锁,所以是类的方法。 使用 sleep 方法可以让让当前线程休眠,时间一到当前线程继续往下执行,在任何地方都能使用,需要捕获 InterruptedException 异常。而使用 wait 方法则必须放在 synchronized 块里面,同样需要捕获 InterruptedException 异常,并且需要获取对象的锁,还需要额外的方法 notify/ notifyAll 进行唤醒,它们同样需要放在 synchronized 块里面,且获取对象的锁。 sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。 sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。

为什么wait定义在object类中?

简单说:因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。 专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。 也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。

实际使用线程池的场景

场景1:快速响应用户请求 用户发起的实时请求,服务追求响应时间。比如说用户要查看一个商品的信息,那么我们需要将商品维度的一系列信息如商品的价格、优惠、库存、图片等等聚合起来,展示给用户。 这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。 场景2:快速处理批量任务,吞吐量优先 离线的大量计算任务,需要快速执行。比如说,统计某个报表,需要计算出全国各个门店中有哪些商品有某种属性 这种场景需要执行大量的任务,我们也会希望任务执行的越快越好。这种情况下,也应该使用多线程策略,并行计算。但与响应速度优先的场景区别在于,这类场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先的问题。所以应该设置队列去缓冲并发任务,调整合适的corePoolSize去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。

单核下多线程i++问题

单核cpu仍然存在线程安全问题,因为如果操作不是原子操作,你无法控制cpu在什么时机切换线程, 自增操作是不具备原子性的,它包含取数据、+1、数据写回操作 看似在单核cpu上是没有线程安全问题。但错了,这里是因为这些过程在一个cpu时间片内执行完了,所以不明显,当把循环次数调大就会有没执行完的情况。

单核下, volatile 修饰的 i 的多线程 i++ 问题

  1. 不用 volatile 假如ready字段不使用volatile,那么Thread 1对ready做出的修改对于Thread2来说未必是可见的,是否可见是不确定的.假如此时thread1 ready泄露了(leak through)了,那么Thread 2可以看见ready为true,但是有可能answer的改变并没有泄露,则thread2有可能会输出 0 (answer=42对thread2并不可见)
  2. 使用 volatile 使用volatile以后,做了如下事情 每次修改volatile变量都会同步到主存中 每次读取volatile变量的值都强制从主存读取最新的值(强制JVM不可优化volatile变量,如JVM优化后变量读取会使用cpu缓存而不从主存中读取) 线程 A 中写入 volatile 变量之前可见的变量, 在线程 B 中读取该 volatile 变量以后, 线程 B 对其他在 A 中的可见变量也可见. 换句话说, 写 volatile 类似于退出同步块, 而读取 volatile 类似于进入同步块 所以如果使用了volatile,那么Thread2读取到的值为read=>true,answer=>42,当然使用volatile的同时也会增加性能开销

注意

volatile并不能保证非源自性操作的多线程安全问题得到解决,volatile解决的是多线程间共享变量的可见性问题,而例如多线程的i++,++i,依然还是会存在多线程问题,它是无法解决了.如下:使用一个线程i++,另一个i–,最终得到的结果不为0。 原因是i++和++i并非原子操作 private static AtomicInteger count = new AtomicInteger(0);

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 崩天的勾玉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程状态周期
    • 进入阻塞状态的原因
      • 进入死亡状态的原因
      • 实现线程的四种方式
      • 线程池
        • 线程池的原理
          • 4种拒绝策略
      相关产品与服务
      GPU 云服务器
      GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档