Java 线程正确打开方式

「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

前言

虽然关于 Java 线程的文章已经被写烂了。但是别人总结的文章只能是别人的知识点,虽然我眼睛看会了,但是脑子是记不住的。所以往后学了知识、看了文章最好还是总结下来才是自己的。

线程是操作系统调度的最小单位。

进程是操作系统分配资源的最小单位。

相信这两句话,大家应该是比较清楚的。这两句话理解也不难,就是从操作系统层面去理解线程与进程的关系。要想理解线程与进程的概念我推荐大佬的文章。

下面就总结一下 Java 中如何使用线程。

正文

线程实现

Java 中线程的实现有很多方式主要是一下两种:

  • 继承 Thread类,并重写 run方法
  • 实现 Runnable 接口的 run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码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接口的方式来自定义线程类。

线程池

实际上创建线程的方式还有一种是通过 CallableFuture 创建。这种方式是为了解决线程运行没有返回值的情况,主要配合线程池使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码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 表示线程没有任务执行时最多保持多久时间会终止
  • unit keepAliveTime 时间单位
  • workQueue 阻塞队列,用于线程池中的线程数目达到corePoolSize后,缓存任务
  • threadFactory 线程工厂,主要用来创建线程
  • handler 拒绝策略,用户设置线程池无法处理任务的规则

关于 Java 中常见的四种线程池:

  • newCachedThreadPool 可缓存线程池,可灵活回收空闲线程
  • newFixedThreadPool 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
  • newScheduledThreadPool 定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor 单线程化的线程池,单线程来执行任务。保证所有任务顺序执行。

《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

参考资料

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%