多线程也是面试必问的东西,我们要了解线程的状态周期,创建线程的方式,以及线程池的使用。
答:
1.等待I/O流的输入输出
2.等待网络资源,即网速问题
3.调用sleep()方法,需要等sleep时间结束
4.调用wait()方法,需要调用notify()唤醒线程
5.其他线程执行join()方法,当前线程则会阻塞,需要等其他线程执行完。
答:
1.线程正常完成工作
2.调用stop()方法,强行停止线程
3.外部原因中断线程
interrupt方法用于中断线程。调用该方法的线程的状态为将被置为"中断"状态。
线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
为了方便管理线程和线程复用,可以使用线程池的方式。
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(抛弃最早进入的任务,尝试将这次的任务放进队列)
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++ 问题
注意
volatile并不能保证非源自性操作的多线程安全问题得到解决,volatile解决的是多线程间共享变量的可见性问题,而例如多线程的i++,++i,依然还是会存在多线程问题,它是无法解决了.如下:使用一个线程i++,另一个i–,最终得到的结果不为0。 原因是i++和++i并非原子操作 private static AtomicInteger count = new AtomicInteger(0);