初尝并行编程

进程被分为后台进程和应用进程

大部分后台进程在系统开始运行时被操作系统启动,完成操作系统的基础服务功能。大部分应用进程由用户启动,完成用户所需的具体应用功能

进程由程序段、数据段、进程控制块三部分组成

程序段也被称为是代码段,代码段是进程的操作数据在内存中的位置,包括需要操作的数据集合;程序控制块包含进程的描述信息和控制信息,是进程的存在的唯一标识

程序控制块(Program Control Block,PCB)包含进程的描述信息和控制信息,是进程存在的唯一标 志。

PCB分为四部分

  • 进程的描述信息
    • 进程ID,进程名称,进程ID是唯一的,进程状态,进程优先级
  • 进程的调度信息
    • 程序起始地址
    • 通信信息
  • 进程的资源信息
    • 内存信息,内存占用情况和内存管理所用的数据结构
    • I/O设备信息
    • 文件句柄
  • 进程的上下文
    • 执行时各种CPU寄存器的值
    • 当前线程计数器
    • 各种栈值信息

线程由三部分组成

  • 线程的描述信息
    • 线程ID
    • 线程名称
    • 线程优先级
    • 线程状态
    • 其他,例如是否为守护线程
  • 程序计数器
  • 栈内存

通过实现Runnable接口的方式创建线程目标类

缺点:

  • 所创建的类并不是线程类,而是线程的执行目标类
  • 如果访问当前线程的属性,不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前的线程实例,才能访问和控制当前线程。

优点:

  • 避免Java单继承的局限性,如果异步逻辑所在的类已经继承了一个基类,就没办法继承Thread类
  • 逻辑和数据更好的分离

继承Thread类实现多线程能更好地做到多个线程并发的完成各自的任务,访问各自的数据

使用Callable和FurtureTask创建线程

RunnableFurture接口

Future接口提供了三大功能:

  • 能够取消异步执行的任务
  • 判断异步任务是否执行完成
  • 获取异步任务完成后的结果

通过线程池创建执行目标提交

private static ExecutorService pool =
Executors.newFixedThreadPool(3);

线程的调度模型

  • 分时调度模型

    系统平均分配CPU时间片

  • 抢占式调度模型

    优先级高的优先分配

线程的生命周期

public static enum State {
 NEW, //新建
 RUNNABLE, //可执行:包含操作系统的就绪、运
行两种状态
 BLOCKED, //阻塞
 WAITING, //等待
 TIMED_WAITING, //限时等待
 TERMINATED; //终止
 }

四种常见状态

  • NEW状态

    创建成功但是没有调用start()方 法启动的Thread线程实例都处于NEW状态。

  • RUNNABLE状态

    当Java 线程的Thread实例的start()方法被调用后,操作系统中的对应线程进 入的并不是运行状态,而是就绪状态,而Java线程并没有这个就绪状 态。

    一旦就绪状态被系统选中,获得CPU时 间片,线程就开始占用CPU,开始执行线程的代码,这时线程的操作系 统状态发生了改变,进入了运行状态。

  • TERMINATED状态

    处于RUNNABLE状态的线程在run()方法执行完成之后就变成终止状 态TERMINATED了。

  • TIMED_WAITING状态

    线程处于一种特殊的等待状态,准确地说,线程处于限时等待状态。

    • Thread.sleep(int n):使得当前线程进入限时等待状态, 等待时间为n毫秒。
    • Object.wait():带时限的抢占对象的monitor锁
    • Thread.join():带时限的线程合并。
    • LockSupport.parkNanos():让线程等待,时间以纳秒为单 位。
    • LockSupport.parkUntil():让线程等待,时间可以灵活设置
  • yield仅能使一个线程从运行状态变为就绪状态,而不是阻塞状态

  • yield不能保证使得当前正在运行的线程迅速转换到就绪状 态。

  • 即使完成了迅速切换,系统通过线程调度机制从所有就绪线 程中挑选下一个执行线程时,就绪的线程有可能被选中,也有可能不 被选中,其调度的过程受到其他因素(如优先级)的影响

守护线程和用户线程

守护线程在JVM中相当于保姆的角色:只 要JVM实例中尚存在任何一个用户线程没有结束,守护线程就能执行自 己的工作;只有当最后一个用户线程结束,守护线程随着JVM一同结束 工作。

守护线程和用户线程的本质区别是:二者与JVM虚拟机进 程终止的方向不同。用户线程和JVM进程是主动关系,如果用户线程全 部终止,JVM虚拟机进程也随之终止;守护线程和JVM进程是被动关 系,如果JVM进程终止,所有的守护线程也随之终止

使用守护线程需要注意的方面:

  • 守护线程必须在启动前将其守护状态设置为true,启动之后 不能再将用户线程设置为守护线程,否则JVM会抛出一个 InterruptedException异常。
  • 守护线程存在被JVM强行终止的风险,所以在守护线程中尽 量不去访问系统资源,如文件句柄、数据库连接等。守护线程被强行 终止时,可能会引发系统资源操作不负责任的中断,从而导致资源不 可逆的损坏。
  • 守护线程创建的线程也是守护线程。

线程进入就绪状态的条件:

  • 调用线程的start()方法,此线程就会进入就绪状态。
  • 当前线程的时间片用完
  • 线程睡眠(Sleep)操作结束
  • 对其他线程合入(Join)操作结束
  • 等待用户输入结束
  • 线程争抢到对象锁(Object Monitor)
  • 当前线程调用了yield()方法让出CPU执行权限

处于阻塞状态的线程不会占用CPU的资源

进入阻塞的情况

  • 线程等待获取锁
  • IO阻塞

线程的创建

  • 必须为线程堆栈分配和初始化大量内存块,其中包含至少 1MB的栈内存
  • 需要进行系统调用,以便在OS(操作系统)中创建和注册本 地线程

线程池解决的问题:

  • 提升性能:线程池能独立负责线程的创建、维护和分配。在 执行大量异步任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大 限度地对已经创建的线程进行复用,使得性能提升明显。
  • 线程管理:每个Java线程池会保持一些基本的线程统计信 息,例如完成的任务数量、空闲时间等,以便对线程进行有效管理, 使得能对所接收到的异步任务进行高效调度。

由于创建和销毁线程需要时间以及系统资源开销,使用线程池的好处是减少这些开销,解决资源不足的问题

JUC的线程架构

  • Executor

    Executor是Java异步目标任务的执行者接口,其目标是执行目标任务,提供了execute()接口来执行已提交的Runnable执行目标实例。

  • ExecutorService

    继承于Executor。它是Java异步目标任务的执行者服务接口,对外提供异步任务的接收服务。

  • AbstractExecutorService

    是一个抽象类,它实现了ExecutorService接口,存在的目的是为了ExecutorService中的接口默认实现

  • ThreadPoolExecutor

    线程池的实现类,继承于AbstractExecutorService抽象类

    JUC线程池的核心实现类 提供了指定数量的可重用线程,所以使用线程需要很大的开销

  • ScheduleExcutorService

    是一个接口可以完成延时和周期性任务的调度线程池的接口。其功能和Timer/TimerTask类似

  • ScheduledThreadPoolExecutor

  • Executors

    静态工厂类,通过静态工厂方法返回线程池的实例对象

    单线程化的线程池中的任务是按照提交次序顺序执行的

    池中唯一线程的存活时间是无限的

    当池中唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列中,并且其阻塞队列是无界的

    用例最后调用shutdown()方法来关闭线程池。

newFixedThreadPool创建“固定数量的线程池”

  • 如果固定数量线程池没有达到“固定数量”,每次提交一个任务线程池就创建一个新的线程,直到线程达到线程池的固定数量。
  • 线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程
  • 在接受异步任务的执行目标实例时,如果池中所有的线程均处于繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)

固定数量线程池适用场景;需要任务长期执行的场景。固定数量的线程池的线程数量能比较稳定的保证一个数,避免频繁的回收线程和创建线程,固适用于处理CPU密集型任务,在CPU被工作线程长时间占用时,能确保尽可能少的分配线程

弊端:

  • 内部使用无界队列来存放排队任 务,当大量任务超过线程池最大容量需要处理时,队列无限增大,使 服务器资源迅速耗尽。

newCachedThreadPool创建“可缓存线程池”

任务被拒绝有两种情况:

  • 线程池已经被关闭
  • 工作队列已满且maxmumPoolSize已满

线程池的拒绝策略

  • AbortPolicy:拒绝策略(线程池默认的策略)

    使用该策略时,如果线程池队列满了,新任务就会被拒绝,并且 抛出RejectedExecutionException异常。该策略是线程池默认的拒绝 策略。

  • DiscardPolicy:抛弃策略

    该策略是AbortPolicy的Silent(安静)版本,如果线程池队列满 了,新任务就会直接被丢掉,并且不会有任何异常抛出。

  • DiscardOldestPolicy:抛弃最老任务策略

    抛弃最老任务策略,也就是说如果队列满了,就会将最早进入队 列的任务抛弃,从队列中腾出空间,再尝试加入队列。因为队列是队 尾进队头出,队头元素是最老的,所以每次都是移除队头元素后再尝 试入队。

  • CallerRunsPolicy:调用者执行策略

    调用者执行策略。在新任务被添加到线程池时,如果添加失败, 那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去 执行新任务。

  • 自定义策略

    如果以上拒绝策略都不符合需求,那么可自定义一个拒绝策略, 实现RejectedExecutionHandler接口的rejectedExecution方法即可。

线程池的五种状态

  • RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务
  • SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行完毕
  • STOP:该状态下线程池不再接受新任务,也不会处理工作队列的剩余任务,并且将会中断所有工作线程。
  • TIDYING:该状态下所有任务都已终止或者处理完成,将会 执行terminated()钩子方法。
  • TERMINATED:执行完terminated()钩子方法之后的状态

线程池的转换规则:

  • 线程池创建之后状态为RUNNING。
  • 执行线程池的shutdown()实例方法,会使线程池状态从 RUNNING转变为SHUTDOWN。
  • 执行线程池的shutdownNow()实例方法,会使线程池状态从 RUNNING转变为STOP。
  • 当线程池处于SHUTDOWN状态时,执行其shutdownNow()方法 会将其状态转变为STOP。
  • 执行完terminated()钩子方法之后,线程池状态从TIDYING 转变为TERMINATED。

优雅的关闭线程池主要涉及的方法有三个:

  • shutdown:是JUC提供的一个有序关闭线程池的方法,此方 法会等待当前工作队列中的剩余任务全部执行完成之后,才会执行关 闭,但是此方法被调用之后线程池的状态转为SHUTDOWN,线程池不会 再接收新的任务
  • shutdownNow:是JUC提供的一个立即关闭线程池的方法,此 方法会打断正在执行的工作线程,并且会清空当前工作队列中的剩余 任务,返回的是尚未执行的任务。
  • awaitTermination:等待线程池完成关闭。在调用线程池的 shutdown()与shutdownNow()方法时,当前线程会立即返回,不会一直 等待直到线程池完成关闭。如果需要等到线程池关闭完成,可以调用 awaitTermination()方法。

使用Executors快捷创建线程池的潜在问题

  • 创建固定数量线程池的潜在问题

    主要存在于其workQueue上,其值为LinkedBlockingQueue(无 界阻塞队列)。如 果任务提交速度持续大于任务处理速度,就会造成队列中大量的任务 等待。如果队列很大,很有可能导致JVM出现OOM(Out Of Memory)异 常,即内存资源耗尽。

  • 使用Executors创建“单线程化线程池”的潜在问题

    潜在问题仍然存在于其workQueue属性上,该属性的值为 LinkedBlockingQueue(无界阻塞队列)。如果任务提交速度持续大于 任务处理速度,就会造成队列大量阻塞。如果队列很大,很有可能导 致JVM的OOM异常,甚至造成内存资源耗尽。

  • 使用Executors创建“可缓存线程池”的潜在问题

    (1)FixedThreadPool和SingleThreadPool 这两个工厂方法所创建的线程池,工作队列(任务排队的队列) 的长度都为Integer.MAX_VALUE,可能会堆积大量的任务,从而导致 OOM(即耗尽内存资源)。

    (2)CachedThreadPool和ScheduledThreadPool 这两个工厂方法所创建的线程池允许创建的线程数量为 Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM。

线程池的好处:

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Array_new

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值