一、使用Thread类
可以使用Thread类来创建一个新的线程,并在其run()方法中执行操作。例如:
public class MiniProgramController extends Thread{
@Override
public void run() {
System.out.println("1111");
}
public static void main(String[] args) {
MiniProgramController min=new MiniProgramController();
min.start();
System.out.println("helloworl");
}
}
二、使用Runnable接口
可以通过实现Runnable接口并在其中实现耗时操作,并通过Thread类来启动新线程。例如:
public class MiniProgramController implements Runnable{
@Override
public void run() {
System.out.println("1111");
}
public static void main(String[] args) {
MiniProgramController min=new MiniProgramController();
min.start();
System.out.println("helloworl");
}
}
三、使用Callable 接口
前面有两种创建线程的方法,一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口。
Callable 接口的特点如下(重点)
• 为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于Callable,需要实现在完成时返回结果的 call()方法。
• call()方法可以引发异常,而 run()则不能。
• 为实现 Callable 而必须重写 call 方法
• 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
public class CallableDemo {
public static void main(String[] args) {
//方式一
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
new Thread(futureTask).start();
//方式二
FutureTask<Integer> integerFutureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName());
return 1024;
});
new Thread(integerFutureTask,"lucy").start();
}
}
由于FutureTask实现的是RunnableFuture接口,而RunnableFuture实现了Runnable和Future。
因此
1. FutureTask可作为Runnable的实现传入Thread构造方法中
2. 需要实现callable中的call方法,这个call方法就是我们需要写的逻辑了,需要让这个线程做什么以及需要返回什么结果
四、使用Executor和ThreadPool
可以使用Executor和ThreadPool来创建和管理线程池,从而执行异步操作。例如:
1、Executor
public class MiniProgramController implements Runnable{
private static ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("1111");
}
});
System.out.println("helloworld");
}
}
2、ThreadPool
1、七个参数
• corePoolSize 线程池的核心线程数
• maximumPoolSize 能容纳的最大线程数
• keepAliveTime 空闲线程存活时间
• unit 存活的时间单位
• workQueue 存放提交但未执行任务的队列
• threadFactory 创建线程的工厂类
• handler 等待队列满后的拒绝策略
线程池中,有三个重要的参数,决定影响了拒绝策略:corePoolSize - 核心线程数,也即最小的线程数。workQueue - 阻塞队列 。 maximumPoolSize - 最大线程数
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。
总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。
2、拒绝策略(重点)
CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
AbortPolicy: 丢弃任务,并抛出拒绝执行RejectedExecutionException 异常 信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
DiscardPolicy: 直接丢弃,其他啥都没有
DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
3、代码
//自定义线程池的创建
public class ThreadPoolDemo3 {
public static void main(String[] args) {
Integer processNum = Runtime.getRuntime().availableProcessors();
int corePoolSize = (int) (processNum / (1 - 0.2));
int maxPoolSize = (int) (processNum / (1 - 0.5));
ExecutorService threadPool = new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 1; i <=10 ; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}
}
备:如果在日常工作中建议使用ThreadPoolExecutor手动创建线程池,并指定合理的线程数量和队列容量,以及适当的拒绝策略,如果直接去使用Executor去线程池有可能会出现OOM的现象。