线程Thread

线程的生命周期分为五个阶段:新建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、死亡(dead)

java 线程 与cpu线程 java线程和线程池_java 线程 与cpu线程

线程概念:程序执行流的最小执行单元,是进程中的实际运作单位。

进程概念:一个应用程序的运行就可以看做是一个进程。

并行:真实的同时运行。

并发:指多个程序可以同时运行的现象,实际上,并不是同时运行的,而是交替进行的(这种同时是一种假象,因为切换很快导致用户感知到是同时)。

Java多线程实现方式主要有三种

  1. 继承Thread类,无返回值
  2. 实现Runnable接口,无返回值
  3. java juc并发工具包里面的类实现多线程,Executor、Executors、ExecutorService、ThreadPoolExecutor、Future、FutureTask、Callable等

线程池

java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。

线程池的优点:方便管理线程,减少内存消耗。

线程池里面执行的是任务,执行多线程任务流程:

1.创建线程池

2.线程池执行任务(任务必须实现Runnable或者Callable),调用executor执行任务无返回值,调用submit方法有返回值,返回一个Future

3.获取返回结果,Future.get()

创建线程池:

Executors工具类来获取线程池,比如Executors.newFixedThreadPool(int nThreads),虽然便捷但是埋下了潜在的隐患(OOM,线程耗尽)。推荐使用ThreadPoolExecutor的构造方法创建线程池。其实Executors方法创建的线程池就是调用ThreadPoolExecutor的构造方法实现的。

// Java线程池的完整构造函数
public ThreadPoolExecutor(
  int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
  int maximumPoolSize, // 线程数的上限
  long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,
                                     // 超过这个时间,多余的线程会被回收。
  BlockingQueue<Runnable> workQueue, // 任务的排队队列
  ThreadFactory threadFactory, // 新线程的产生方式
  RejectedExecutionHandler handler) // 拒绝策略

java 线程 与cpu线程 java线程和线程池_java_02

ThreadPoolExecutor中参数详解

  • corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
  • maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
  • keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
  • unit:keepAliveTime的时间单位
  • workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
  • threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
  • handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy

线程池中的线程创建流程图:

java 线程 与cpu线程 java线程和线程池_多线程_03

常见的通过Executors工具类来创建线程池方式:

1. Executors.newFixedThreadPool(int nThread);定长线程池,有核心线程,核心线程即为最大的线程数量,没有非核心线程。缺点:任务可以无限放入,当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常。

2. Executors.newSingleThreadExecutor():创建只有一个线程的线程池,缺点与newFixedThreadPool一致。

3. Executors.newCachedThreadPool():可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Interger.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。缺点:由于将maxPoolSize设置成Integer.MAX_VALUE,当请求很多时就可能创建过多的线程,导致资源耗尽OOM

4. Executors.newScheduledThreadPool():周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也是无限大。适用于执行周期性的任务。缺点与newCachedThreadPool一致。

注:《阿里巴巴开发规范》不允许使用Executors创建线程池,推荐通过ThreadPoolExecutor的方式创建,优点:让程序员明确线程池的运行规则,规避资源耗尽的风险。

Executors.newFixedThreadPool和Executors.newSingleThreadExecutor风险:允许的请求队列长度为Interger.max_value,可能会堆积大量的请求,从而导致oom

Executors.newCachedThreadPool和Executors.newScheduledThreadPool风险:允许的创建线程数为Interger.max_value,可能会创建大量的线程,从而导致oom

Callable与Runnable

1.实现类都可以向线程池提交任务。

2.方法签名不同,void Runnable.run(), V Callable.call() throws Exception

3.是否允许有返回值,Callable允许有返回值

4.是否允许抛出异常,Callable允许抛出异常。

三种提交任务的方式

1.Future<T> submit(Callable<T> task) 有返回值

2.void execute(Runnable command) 无返回值

3.Future<?> submit(Runnable task) 虽然返回Future,但是get方法总是返回null

Future和FutureTask

Future接口用来表示执行异步任务的结果存储器,当一个任务的执行时间过长就可以采用这种方式:把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果。

Future的主要方法:

get()方法:返回任务的执行结果,若任务还未执行完,则会一直阻塞直到完成为止,如果执行过程中发生异常,则抛出异常,但是主线程是感知不到并且不受影响的,除非调用get()方法进行获取结果则会抛出ExecutionException异常;

get(long timeout, TimeUnit unit):在指定时间内返回任务的执行结果,超时未返回会抛出TimeoutException,这个时候需要显式的取消任务;

cancel(boolean mayInterruptIfRunning):取消任务,boolean类型入参表示如果任务正在运行中是否强制中断;

isDone():判断任务是否执行完毕,执行完毕不代表任务一定成功执行,比如任务执行失但也执行完毕、任务被中断了也执行完毕都会返回true,它仅仅表示一种状态说后面任务不会再执行了;

isCancelled():判断任务是否被取消;

FutureTask 是一个类,可以直接创建对象,其实现了RunnableFuture接口(继承Future接口)

wait、await、sleep、yield、join方法比较

1. wait()、notify()、notifyAll()

obj.wait()/obj.wait(long timeout)是Object中的方法,当线程调用wait()方法,当前线程释放对象锁,进入等待队列。

obj.notify()/obj.nogifyAll()是Object中的方法,唤醒在此对象上wait()的单个或者所有线程。

2. await()、signal()、signalAll()

java.util.concurrent类库中提供的Condition类来实现线程之间的协调。

condition.await()/condition.await(long time, TimeUnit unit):通过condition调用,当前线程释放对象锁。

condition.signal()/condition.signalAll():通过signal或者signalAll方法唤醒await挂起的线程。

3. yield()、join()

Thread.yield():一定是当前线程调用此方法,当前线程放弃获取CPU的时间片,由运行态转变为就绪态,让操作系统中再次选择线程执行。作用:让相同优先级的线程轮流执行,但并不能保证轮流执行,根据解释我们了解到,转成就绪态的的线程还有可能再次选中执行。Thread.yield()方法不会导致阻塞。

t.join()/t.join(long millis):当前线程调用t2.join()方法,当前线程阻塞但是不会释放对象锁,直到t2线程执行完毕或者millis时间到,则当前的线程恢复就绪状态。作用:让优先级比较高的线程优先执行。

4. wait()、await()、sleep()、yield、join 对比

比较项目

wait

await

sleep

yield

join

是否释放持有锁

释放

释放

不释放

不释放

t1不释放

谁的方法

Object

Condition

Thread

Thread

线程对象

唤醒方法

nogify/nogifyAll

signal/signalAll

指定时间后

自动唤醒

t2执行完自动唤醒

何时就绪

唤醒后就绪

唤醒后就绪

指定时间后就绪

立刻进入就绪

t2完成后进入就绪

执行环境

同步代码块

同步代码块

任意位置

任意位置

任意位置