线程相关知识合集

本文主要总结关于线程相关的知识点,线程对于大家来说应该再熟悉不过了。不过,对于有些不常用的,我觉得还是得记下来,说不定那个时候就需要用上了呢?


线程简单介绍

  • 不带返回参数
    • 直接继承Thread
    • 实现Runnable接口
  • 带返回参数
    • 实现Callable

这里着重介绍不常用的Callable

Callable简介

Callable位于java.util.concurrent包下,它是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():

1
2
3
4
5
6
7
8
9
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}

这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。

Callable使用介绍

ExecutorService接口了解

Callable一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载

1
2
3
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

这里有一个类,Future,它是做什么的呢?接着往下看

Future接口了解

它的作用就是对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

Future类位于java.util.concurrent包下,它是一个接口:

1
2
3
4
5
6
7
8
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

方法介绍:

  • cancel方法

用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。

  • isCancelled方法

表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

  • isDone方法

表示任务是否已经完成,若任务完成,则返回true;

  • get()方法

用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

  • get(long timeout, TimeUnit unit)方法

用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

  也就是说Future提供了三种功能:

  1)判断任务是否完成;

  2)能够中断任务;

  3)能够获取任务执行结果。

  因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

FutureTask类了解

utureTask是Future接口的一个唯一实现类

  • FutureTask的实现
    1
    public class FutureTask<V> implements RunnableFuture<V>

FutureTask类实现了RunnableFuture接口

  • RunnableFuture接口的实现
    1
    2
    3
    public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
    }

从上面看,RunnableFuture 继承了 Runnable, Future,所以,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

FutureTask提供了2个构造器:

1
2
3
4
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}

使用示例

  • 使用Callable+Future获取执行结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class Test {
    public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    Future<Integer> result = executor.submit(task);
    executor.shutdown();

    try {
    Thread.sleep(1000);
    } catch (InterruptedException e1) {
    e1.printStackTrace();
    }

    System.out.println("主线程在执行任务");

    try {
    System.out.println("task运行结果"+result.get());
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }

    System.out.println("所有任务执行完毕");
    }
    }
    class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
    System.out.println("子线程在进行计算");
    Thread.sleep(3000);
    int sum = 0;
    for(int i=0;i<100;i++)
    sum += i;
    return sum;
    }
  • 使用Callable+FutureTask获取执行结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class Test {
    public static void main(String[] args) {
    //第一种方式
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    executor.submit(futureTask);
    executor.shutdown();

    //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
    /*Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    Thread thread = new Thread(futureTask);
    thread.start();*/

    try {
    Thread.sleep(1000);
    } catch (InterruptedException e1) {
    e1.printStackTrace();
    }

    System.out.println("主线程在执行任务");

    try {
    System.out.println("task运行结果"+futureTask.get());
    } catch (InterruptedException e) {
    e.printStackTrace();
    } catch (ExecutionException e) {
    e.printStackTrace();
    }

    System.out.println("所有任务执行完毕");
    }
    }
    class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
    System.out.println("子线程在进行计算");
    Thread.sleep(3000);
    int sum = 0;
    for(int i=0;i<100;i++)
    sum += i;
    return sum;
    }
    }
    特别提醒:
  • 如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。
  • get()方法有阻塞性

线程池

线程池的定义:缓存了一定线程数量的区域。

它主要负责:

  • 重用线程
  • 控制线程池的最大并发数
  • 管理线程 (分配、调优、监控)

Java通过Executors提供四种线程池,分别为:

  • CachedThreadPool newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • FixedThreadPool newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • ScheduledThreadPool newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
  • SingleThreadExecutor newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

为什么要使用线程池

new Thread的弊端:

  • 每次new Thread新建对象性能差。
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  • 缺乏更多功能,如定时执行、定期执行、线程中断。

相比new Thread,Java提供的四种线程池的好处在于:

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

线程池使用流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 创建线程池,通过配置线程池的参数,从而实现自己所需的线程池
Executor threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);
// 2. 向线程池提交任务:execute() 说明:传入 Runnable对象
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行任务
}
});

// 3. 关闭线程池shutdown()
threadPool.shutdown();
  • 关闭线程的原理
    a. 遍历线程池中的所有工作线程
    b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)

也可调用shutdownNow()关闭线程:threadPool.shutdownNow()

  • 二者区别:
    a. shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
    b. shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

线程池内部原理逻辑

四种线程池使用介绍

线程池中有6个核心参数,详细如下:

参数 意义 说明
corePoolSize 核心线程数 默认情况下,核心线程一直存活(包括空闲状态)
maximumPoolSize 线程池所能容纳的最大线程数 当活动线程数到达该数值后,后续的新任务将会阻塞
keepAliveTime 非核心线程,闲置超时时长 超过该时长,非核心线程就会被回收(当将 allowCoreThreadTimeout设置为true时,keepAliveTime同样作用于核心线程)
unit 指定keepAliveTime参数的时间单位 常用:(毫秒 TimeUnit.MILLISECONDS、秒 TimeUnit.SECONDS)
workQueue 任务队列 通过线程池的execute()方法提交的Runnable对象,将存储在该参数中
threadFactory 线程工厂 作用:为线程池创建新线程,具体使用:只有一个方法 Thread.newThread(Runnable)

可缓存线程池(CachedThreadPool)

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,如果没完成则新建线程。(任何线程任务到来都会立刻执行,不需要等待)

  • 一般用于执行大量、耗时少的线程任务
1
2
3
4
5
6
7
// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务 略
/ 3. 向线程池提交任务:execute()
cachedThreadPool.execute(task);
// 4. 关闭线程池
cachedThreadPool.shutdown();

定时线程池(ScheduledThreadPool )

只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时) 【任何线程任务到来都会立刻执行,不需要等待】

  • 一般用于执行定时 / 周期性 任务
1
2
3
4
5
6
7
8
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务 略
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();

定长线程池(FixedThreadPool)

只有核心线程不会回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待)

  • 一般用于控制线程最大并发数
1
2
3
4
5
6
7
/ 1. 创建定长线程池对象 & 设置线程池线程数量固定为10
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
// 2. 创建好Runnable类线程对象 & 需执行的任务略
// 3. 向线程池提交任务:execute()
fixedThreadPool.execute(task);
// 4. 关闭线程池
fixedThreadPool.shutdown();

单线程化线程池(SingleThreadExecutor)

只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)

  • 不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等
1
2
3
4
5
6
7
8
9
10
11
12
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("执行singleThreadExecutor的任务啦");
}
};
// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);
// 4. 关闭线程池
singleThreadExecutor.shutdown();

相关参考