Java线程

线程状态

6个状态定义:java.lang.Thread.State

  1. New:尚未启动的线程的线程状态。
  2. Runnable:可运行线程的线程状态,等待 CPU 调度
  3. Blocked:线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或方法中被阻塞。
  4. Waiting:等待线程的线程状态。下列不带超时的方式:Object.waitThread.joinLockSupport.park
  5. Timed Waiting:具有指定等待时间的等待线程的线程状态。Thread.sleepObject.waitThread.joinLockSupport.parkNanosLockSupport.parkUtil
  6. Terminated:终止线程的线程状态。线程正常完成执行或者出现异常。线程状态

线程中止
  1. 不正确的线程中止 - Stop
    Stop:中止线程,并且清除监控器锁的信息,但是可能导致线程安全问题,JDK 不建议用。
    Destroy:JDK未实现该方法。
  2. 正确的线程中止 - interrupt
    • 如果目标线程在调用 Object class 的 wait()wait(long)wait(long, int) 方法、join()join(long, int)sleep(long, int) 方法时被阻塞,那么 Interrupt 会生效,该线程的中断状态将被清除,抛出 InterruptedException 异常。
    • 如果目标线程是被 I/O 或者 NIO 中的 Channel 所阻塞,同样,I/O 操作会被中断或者返回特殊异常值。达到终止线程的目的。
    • 如果以上条件都不满足,则会设置此线程的中断状态。
  3. 正确的线程中止 - 标志位
    代码逻辑中,增加一个判断,用来控制线程执行的中止。

线程通信

通信的方式

要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。

这涉及到线程之间相互通信,分为下面四类:

  1. 文件共享
  2. 网络共享
  3. 共享变量
  4. jdk 提供的线程协作 API
    细分为:suspend/resumewait/notifypark/unpark
  • 文件共享
    文件共享
  • 变量共享
    变量共享
  • 线程协作 - JDK API

JDK 中对于需要多线程协作完成某一任务的场景,提供了对应 API 支持。
多线程协作的典型场景是:生产者 - 消费者模型。(线程阻塞、线程唤醒)

  1. 被弃用的 suspendresume
    作用:调用 suspend 挂起目标线程,通过 resume 可以恢复线程执行。
    被弃用的主要原因是,容易写出死锁的代码。
    所以用 wait/notifypark/unpark 机制对它进行替代
  2. wait/notify 机制
    这些方法只能由同一对象锁的持有者线程调用,也就是写在同步块里面,否则会抛出 IllegalMonitorStateException 异常。
    wait 方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
    notify/notifyAll 方法唤醒一个或所有正在等待这个对象锁的线程。
    注意:虽然 wait 会自动解锁,但是对顺序有要求,如果 notify 在被调用之后,才开始 wait 方法的调用,线程会永远处于 WAITING 状态。
  3. park/unpark 机制
    线程调用 park 则等待“许可”,unpark 方法为指定线程提供“许可(permit)”。
    不要求 parkunpark 方法的调用顺序。
    多次调用 unpark 之后,再调用 park,线程会直接运行。
    不会叠加,也就是说,连续多次调用 park 方法,第一次会拿到“许可”直接运行,后续调用会进入等待。
    注意:在同步代码中,park/unpark 容易写出死锁代码。

伪唤醒
代码中用 if 语句来判断线程是否进入等待状态,是错误的!
官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
伪唤醒是指线程并非因为 notifynotifyAllunpark 等 api 调用而唤醒,是更底层原因导致的。

// wait
synchronized(obj) {
	while(<条件判断>) {
		obj.wait();
		...// 执行后续操作
	}
}
// park
while(<条件判断>) {
	LockSupport.park();
	...// 执行后续操作
}

线程封闭

概念:多线程访问共享可变数据时,涉及到线程间数据同步的问题。并不是所有时候,都要用到共享数据,所以线程封闭概念就提出来了。
数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭
线程封闭具体的体现有:ThreadLocal局部变量

  • ThreadLocal
    ThreadLocal 是 Java 里一种特殊的变量。
    它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。
    用法:ThreadLocal<T> var = new ThreadLocal<T>();
    会自动在每一个线程上创建一个 T 的副本,副本之间彼此独立,互不影响。
    可以用 ThreadLocal 存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法。
    这里作为一个概念理解,可以理解为,JVM 维护了一个 Map<Thread,T>,每个线程要用这个 T 的时候,用当前的线程去 Map 里面取。

栈封闭
局部变量的固有属性之一就是封闭在线程中。
它们位于执行线程的栈中,其他线程无法访问这个栈。


线程池原理

线程是不是越多越好?

  1. 线程在 java 中是一个对象,更是操作系统的资源,线程创建、销毁需要时间。如果创建时间 + 销毁时间 > 执行任务时间 就很不合算。
  2. java 对象占用堆内存,操作系统线程占用系统内存,根据 jvm 规范,一个线程默认最大栈大小 1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的内存。
  3. 操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
    线程池的推出,就是为了方便的控制线程数量。

线程池原理 - 概念

  1. 线程池管理器:用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
  2. 工作线程:线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
  3. 任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。

线程池 API - 接口定义和实现类

类型名称描述
接口Executor最上层的接口,定义了执行任务的方法execute
接口ExecutorService继承了Executor接口,拓展了CallableFuture、关闭方法
接口ScheduledExecutorService继承了ExecutorService,增加了定时任务相关的方法
实现类ThreadPoolExecutor基础、标准的线程池实现
实现类ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,实现了ScheduledExecutorService中相关定时任务的方法

线程池 API - 方法定义
ExecutorService

// 监测ExecutorService是否已经关闭,直到所有任务完成执行,或超时发生,或当前线程被中断
awaitTermination(long timeout, TimeUnit unit)
// 执行给定的任务集合,执行完毕后,返回结果
invokeAll(Collection<? extends Callable<T>> tasks)
// 执行给定的任务集合,执行完毕或者超时后,返回结果,其他任务终止
invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
// 执行给定的任务,任意一个执行成功则返回结果,其他任务终止
invokeAny(Collection<? extends Callable<T>> tasks)
// 执行给定的任务,任意一个执行成功或者超时后,则返回结果,其他任务终止
invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
// 如果此线程池已关闭,则返回true。
isShutdown()
// 如果关闭后所有任务都已完成,则返回true。
isTerminated()
// 优雅关闭线程池,之前提交的任务将被执行,但是不会接受新的任务。
shutdown()
// 尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行任务的列表。
shutdownNow()
// 提交一个用于执行的Callable返回任务,并返回一个Future,用于获取Callable执行结果
submit(Callable<T> task)
// 提交可运行任务以执行,并发回一个Future对象,执行结果为null 
submit(Runnable task)
// 提交可运行任务以执行,并返回Future,执行结果为传入的result 
submit(Runnable task, T result)

ScheduledExecutorService

// 创建并执行一个一次性任务,过了延迟时间就会被执行
schedule(Callable<V> callable, long delay, Timeunit unit)
schedule(Runnable command, long delay, TimeUnit unit)
// 创建并执行一个周期性任务,过了给定的初始延迟时间,会第一次被执行。
// 执行过程中发生了异常,那么任务就停止。
// 一次任务执行时长超过了周期时间,下一次任务会等到该次任务执行结束后,立刻执行
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
// 创建并执行一个周期性任务,过了初始延迟时间,第一次被执行,后续以给定的周期时间执行。
// 执行过程中发生了异常,那么任务就停止。
// 一次任务执行时长超过了周期时间,下一次任务会在该次任务执行结束的时间基础上,计算执行延时。
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

线程池 API - Executors 工具类
你可以自己实例化线程池,也可以用Executors创建线程池的工厂类,常用方法如下:
newFixed ThreadPool(int nThreads)创建一个固定大小、任务队列容量无界的线程池。核心线程数 = 最大线程数。
newCachedThreadPool()创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化。适用于执行耗时较小的异步任务。池的核心线程数 = 0,最大线程数 = Integer.MAX_VALUE
newSingleThreadExecutor()只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务按加入的顺序一个一个依次执行。当唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。与newFixed ThreadPool(1)区别在于,单一线程池的池大小在newSingleThreadExecutor方法中硬编码,不能再改变的。
newScheduledThreadPool(int corePoolSize)能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数 = Integer.MAX_VALUE

线程池原理 - 任务 execute 过程

  1. 是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
  2. 工作队列是否已满?没满,则将新提交的任务存储在工作队列里。
  3. 是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。
  4. 最后,执行拒绝策略来处理这个任务。
    任务execute过程
    线程数量
    如何确定合适数量的线程?(可以监控CPU利用率:80%可以作为一个指标)
    计算型任务:cpu数量的1-2倍
    IO型任务:相对比计算型任务,需多一些线程,要根据具体的IO阻塞时长进行考量决定。
    如tomcat中默认的最大线程数为:200。
    也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值