怎么创建线程

0.任务和线程

  • Thread :“线程本体”,负责跟操作系统打交道。

  • Runnable / Callable :只是 “任务”,不是线程

任务必须交给 Thread 去包装、去启动,才能变成真正的线程

FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread t = new Thread(task);

// 启动
t.start();

1. 继承 Thread 类

步骤:

  1. 写一个类 extends Thread

  2. 重写 run() 方法(线程要做的事写这里)

  3. 创建对象,调用 start() 启动线程

代码示例

// 1. 定义线程类
class MyThread extends Thread {
    @Override
    public void run() {
        // 这里面就是线程要执行的代码
        for (int i = 0; i < 5; i++) {
            System.out.println("线程1运行:" + i);
        }
    }
}

// 2. 使用
public class Test {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t = new MyThread();
        // 启动线程(必须调用 start(),不能直接调用 run())
        t.start();
    }
}

关键点:

  • run() 是线程执行体

  • start() 才是真正启动线程(操作系统调度)

  • 直接调用 run() 只是普通方法,不是多线程


2. 实现 Runnable 接口(最常用、推荐)

因为 Java 只能单继承,所以用接口更灵活

步骤:

  1. implements Runnable

  2. 重写 run()

  3. 丢给 Thread 执行

代码

class MyTask implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("线程2运行:" + i);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建任务
        MyTask task = new MyTask();
        // 把任务交给线程
        Thread t = new Thread(task);
        // 启动
        t.start();
    }
}

3. 实现 Callable 接口(可以有返回值)

前两种 run() 都 没有返回值、不能抛异常

想拿到线程执行结果,用 Callable + FutureTask

可以通过 FutureTaskget()方法来获取返回值。

步骤:

  1. implements Callable

  2. 重写 call()

  3. 丢给 Thread 执行

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 100; // 线程执行完返回结果
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FutureTask<Integer> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();

        // 获取线程返回结果
        Integer result = task.get();
        System.out.println(result); // 100
    }
}

线程池

ThreadPoolExecutor参数

其需要如下几个参数:

  1. corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

  2. maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。

  3. workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。

  4. keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

  5. unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

  6. threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。

  7. handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程池原理

当一个任务被提交到线程池之后:

  1. 如果此时线程数小于核心线程数,那么就会新起一个线程来执行当前的任务。

  2. 如果此时线程数大于核心线程数,那么就会将任务塞入阻塞队列中,等待被执行。

  3. 如果阻塞队列满了,并且此时线程数小于最大线程数,那么会创建新线程来执行当前任务。

  4. 如果阻塞队列满了,并且此时线程数大于最大线程数,那么会采取拒绝策略。

提交任务的方法:

  1. execute ():只管执行,不关心结果

  2. submit ():执行任务,并且能拿到返回值、异常

    1. Runnable接口不会返回任何结果或抛出异常,但是Callable接口可以。

    2. submit 接收 Callable,返回一个 Future 类型的对象,

      1. future.get()阻塞主线程等待结果

      2. future.isDone():判断任务是否完成

      3. future.cancel():取消任务

threadPool.execute(() -> {
    System.out.println("我在执行任务");
});

Future<String> future = threadPool.submit(() -> {
    // 业务逻辑
    return "执行结果";
});
try {
    // 阻塞,直到线程执行完
    String result = future.get();
    System.out.println(result);
} catch (Exception e) {
    e.printStackTrace();
}

线程池使用示例:Runnable+ThreadPoolExecutor

首先创建一个 Runnable 接口的实现类:

import java.util.Date;

/**
 * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
 * @author shuang.kou
 */
public class MyRunnable implements Runnable {

    private String command;

    public MyRunnable(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return this.command;
    }
}

使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


public class ThreadPoolExecutorDemo {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    
    public static void main(String[] args) {

    //使用阿里巴巴推荐的创建线程池的方式
    //通过ThreadPoolExecutor构造函数自定义参数创建
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadPoolExecutor.CallerRunsPolicy());

    for (int i = 0; i < 10; i++) {
        //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
        Runnable worker = new MyRunnable("" + i);
        //执行Runnable
        executor.execute(worker);
    }
    
    //终止线程池
    executor.shutdown();
    while (!executor.isTerminated()) {
    
    }
    System.out.println("Finished all threads");
}

任务队列:

用于保存等待执行的任务的阻塞队列。当所有的核心线程被占满时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

拒绝策略:

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK提供的四种策略。