怎么创建线程
0.任务和线程
Thread :“线程本体”,负责跟操作系统打交道。
Runnable / Callable :只是 “任务”,不是线程。
任务必须交给 Thread 去包装、去启动,才能变成真正的线程。
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread t = new Thread(task);
// 启动
t.start();1. 继承 Thread 类
步骤:
写一个类 extends Thread
重写 run() 方法(线程要做的事写这里)
创建对象,调用 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 只能单继承,所以用接口更灵活。
步骤:
类 implements Runnable
重写 run()
丢给 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。
可以通过 FutureTask 的 get()方法来获取返回值。
步骤:
类 implements Callable
重写 call()
丢给 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参数
其需要如下几个参数:
corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
线程池原理
当一个任务被提交到线程池之后:
如果此时线程数小于核心线程数,那么就会新起一个线程来执行当前的任务。
如果此时线程数大于核心线程数,那么就会将任务塞入阻塞队列中,等待被执行。
如果阻塞队列满了,并且此时线程数小于最大线程数,那么会创建新线程来执行当前任务。
如果阻塞队列满了,并且此时线程数大于最大线程数,那么会采取拒绝策略。
提交任务的方法:
execute ():只管执行,不关心结果
submit ():执行任务,并且能拿到返回值、异常
Runnable接口不会返回任何结果或抛出异常,但是Callable接口可以。
submit 接收 Callable,返回一个 Future 类型的对象,
future.get():阻塞主线程等待结果future.isDone():判断任务是否完成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提供的四种策略。