「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
前言
虽然关于 Java 线程的文章已经被写烂了。但是别人总结的文章只能是别人的知识点,虽然我眼睛看会了,但是脑子是记不住的。所以往后学了知识、看了文章最好还是总结下来才是自己的。
线程是操作系统调度的最小单位。
进程是操作系统分配资源的最小单位。
相信这两句话,大家应该是比较清楚的。这两句话理解也不难,就是从操作系统层面去理解线程与进程的关系。要想理解线程与进程的概念我推荐大佬的文章。
下面就总结一下 Java 中如何使用线程。
正文
线程实现
Java 中线程的实现有很多方式主要是一下两种:
- 继承
Thread类,并重写run方法 - 实现
Runnable接口的run方法
public class ThreadDemo {
public static void main(String[] args) {
Thread t1 = new ExtendThread();
t1.start();
Thread t2 = new Thread(new ImpRunnable());
t2.start();
}
}
class ExtendThread extends Thread {
@Override
public void run(){
System.out.println("Extends Thread success");
}
}
class ImpRunnable implements Runnable {
@Override
public void run() {
System.out.println("Implements Runnable success");
}
}
那么为什么对于线程有两种方式去初始化,继承Thread类或者实现Runnable接口有什么区别呢?
Thread类是 Java 实现Runnable接口的严格封装,因此只有当修改或扩展时,才应该继承该类Runnable接口出现更符合面向对象思想(单继承多实现),创建线程更轻量化。
总结:推荐优先使用实现Runnable接口的方式来自定义线程类。
线程池
实际上创建线程的方式还有一种是通过 Callable 和 Future 创建。这种方式是为了解决线程运行没有返回值的情况,主要配合线程池使用。
class ImpCallable implements Callable<Integer> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建线程池
ExecutorService executor = Executors.newCachedThreadPool();
ImpCallable task = new ImpCallable();
// 获取任务结果
Future<Integer> result = executor.submit(task);
System.out.println("Task success and result is " + result.get());
}
@Override
public Integer call() throws Exception {
System.out.println("Implements Callable success");
return 100;
}
}
注意这里的 Executors 就是 Java 中用于创建线程池的工厂方法。返回的线程池都实现了 ExecutorService 接口。
关于线程池的初始化主要是 ThreadPoolExecutor 类
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (corePoolSize >= 0 && maximumPoolSize > 0 && maximumPoolSize >= corePoolSize && keepAliveTime >= 0L) {
if (workQueue != null && threadFactory != null && handler != null) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
主要的参数解释如下:
corePoolSize核心线程数maximumPoolSize最大线程数,表示在线程池中最多能创建多少个线程keepAliveTime表示线程没有任务执行时最多保持多久时间会终止unitkeepAliveTime 时间单位workQueue阻塞队列,用于线程池中的线程数目达到corePoolSize后,缓存任务threadFactory线程工厂,主要用来创建线程handler拒绝策略,用户设置线程池无法处理任务的规则
关于 Java 中常见的四种线程池:
newCachedThreadPool可缓存线程池,可灵活回收空闲线程newFixedThreadPool定长线程池,可控制线程最大并发数,超出的线程会在队列中等待newScheduledThreadPool定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor单线程化的线程池,单线程来执行任务。保证所有任务顺序执行。
《阿里巴巴 Java 开发手册》中强制线程池不允许使用
Executors去创建,而是通过ThreadPoolExecutor构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。