多线程

作者: 流萤飘枫 | 来源:发表于2019-07-23 11:18 被阅读0次

多线程使用场景:

主要是提升性能,降低延迟,提高吞吐量。

最佳线程数原则:将硬件的性能发挥到极致。

最佳线程数 = CPU核数 * [1+(i/o耗时 / cpu 耗时)]

数据库连接池通过线程封闭技术,保证一个 Connection一旦被一个线程获取之后,在这个线程关闭connnection之前的这段时间里,不会再分配给其他线程,从而保证了connection不会有并发问题。

栈溢出原因:

因为每调用一个方法就会在栈上创建一个栈帧,方法调用结束后就会弹出该栈帧,而栈的大小不是无限的,所以递归调用次数过多的话就会导致栈溢出。而递归调用的特点是每递归一次,就要创建一个新的栈帧,而且还要保留之前的环境(栈帧),直到遇到结束条件。所以递归调用一定要明确好结束条件,不要出现死循环,而且要避免栈太深。

解决方法:

1. 简单粗暴,不要使用递归,使用循环替代。缺点:代码逻辑不够清晰;

2. 限制递归次数;

3. 使用尾递归,尾递归是指在方法返回时只调用自己本身,且不能包含表达式。编译器或解释器会把尾递归做优化,使递归方法不论调用多少次,都只占用一个栈帧,所以不会出现栈溢出。然鹅,Java没有尾递归优化。

引起线程上下文切换的原因:

1. 当前执行任务的时间片用完之后,系统CPU正常调度下一个任务;

2. 当前执行任务碰到IO阻塞,调度器将此任务挂起,继续下一任务;

3. 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;

4. 用户代码挂起当前任务,让出CPU时间;

5. 硬件中断;

Object中 notify()和notifyAll()中,除非经过深思熟虑,否则尽量使用notifyAll()。

使用notify()需要满足3个条件:

  1.所有等待线程拥有相同的等待条件。

  2.所有等待线程被唤醒后,执行相同的操作。

  3.只需要唤醒一个线程。

线程的5个状态:

初始状态:指的是线程已经被创建,但是还不允许分配 CPU 执行。

运行状态:指的是线程可以分配 CPU 执行。当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程。

休眠状态:运行状态的线程如果调用一个阻塞的API或者等待某个事件。

终止状态:线程执行完成或者发生异常。

BLOCKED、WAITING、TIMED_WAITING 可以理解为线程导致休眠状态的三种原因。

1.runnable 与blocked状态转换:

synchronized修饰的方法、代码块同一时刻只允许一个线程执行,其他线程只能等待,这种情况下,等待的线程就会从runnable转换到blocked状态。

2.runnable与waiting状态转换:

第一种场景,获得synchronized隐式锁的线程,调用无参的object.wait()方法。

第二种场景,调用无参的thread.join()方法。

第三种场景,调用LockSupport.park()方法。调用LockSupport.park()方法,当前线程会阻塞,线程的状态会从runnable转换到waiting。

3.runnable与time_waiting的状态转换。

一。调用带超时参数的 Thread.sleep(long millis)方法。

二。获得synchronized隐式锁的线程,调用带超时参数的Object.wait(long timeout)方法。

三。调用带超市参数的Thread.join()方法。

四。调用带超时参数的LockSupport.parkNanos(Object blocker,long deadline)方法。

五。调用带超时参数的LockSupport.parkUntil(long deadline)方法。

stop()和interrupt()方法的主要区别:

stop()方法会真的杀死线程不给喘息的机会,如果线程有reentrantLock锁,被stop的线程不会自动调用ReentrantLock的unlock()方法释放锁。累似的方法还有 suspend()和resume()方法。

interupt()方法仅仅是通知线程,线程有机会执行一些后续操作,同时也可以无视这个通知。被interrupt的线程是如何收到通知的呢?一种是异常,一种是主动检车。

当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException 异常。

如果线程处于 RUNNABLE 状态,并且没有阻塞在某个 I/O 操作上,例如中断计算圆周率的线程 A,这时就得依赖线程 A 主动检测中断状态了。如果其他线程调用线程 A 的 interrupt() 方法,那么线程 A 可以通过 isInterrupted() 方法,检测是不是自己被中断了。

ThreadPoolExecutor(

  int corePoolSize,

  int maximumPoolSize,

  long keepAliveTime,

  TimeUnit unit,

  BlockingQueue<Runnable> workQueue,

  ThreadFactory threadFactory,

  RejectedExecutionHandler handler)

corePoolSize:表示线程池保有的最小线程数。有些项目很闲,但是也不能把人都撤了,至少要留 corePoolSize 个人坚守阵地。

maximumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要加人,但是也不能无限制地加,最多就加到 maximumPoolSize 个人。当项目闲下来时,就要撤人了,最多能撤到 corePoolSize 个人。

keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了。

workQueue:工作队列,和上面示例代码的工作队列同义。

threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。

handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。ThreadPoolExecutor 已经提供了以下 4 种策略。

CallerRunsPolicy:提交任务的线程自己去执行该任务。

AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。

DiscardPolicy:直接丢弃任务,没有任何异常抛出。

DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

不建议使用 Executors 的最重要的原因是:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列。

使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 它,所以开发人员很容易忽略。因此默认拒绝策略要慎重使用。如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。

线程池和普通的池化资源有很大不同,线程池实际上是生产者 - 消费者模式的一种实现。

CompletableFuture:

  对于简单的并行任务,你可以通过“线程池 +Future”的方案来解决;如果任务之间有聚合关系,无论是 AND 聚合还是 OR 聚合,都可以通过 CompletableFuture 来解决.

CompletionService:

  当需要批量提交异步任务的时候建议你使用 CompletionService。CompletionService 将线程池 Executor 和阻塞队列 BlockingQueue 的功能融合在了一起,能够让批量异步任务的管理更简单。除此之外,CompletionService 能够让异步任务的执行结果有序化,先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如 Forking Cluster 这样的需求。

  CompletionService 的实现类 ExecutorCompletionService,需要你自己创建线程池,虽看上去有些啰嗦,但好处是你可以让多个 ExecutorCompletionService 的线程池隔离,这种隔离性能避免几个特别耗时的务拖垮整个应用的风险。

Fork/Join:

  分治任务模型可分为两个阶段:一个阶段是任务分解,也就是将任务迭代地分解为子任务,直至子任务可以直接计算出结果;

  另一个阶段是结果合并,即逐层合并子任务的执行结果,直至获得最终结果。下图是一个简化的分治任务模型图,你可以对照着理解。

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

      本文链接:https://www.haomeiwen.com/subject/phkolctx.html