如何创建一个线程

按 Java 语言规范中的说法,创建线程只有一种方式,就是创建一个 Thread 对象。而从 HotSpot 虚拟机的角度看,创建一个虚拟机线程
有两种方式,一种是创建 Thread 对象,另一种是创建 一个本地线程,加入到虚拟机线程中。

如果从 Java 语法的角度。有两种方法。

第一是继承 Thread 类,实现 run 方法,并创建子类对象。

1
2
3
4
5
6
7
8
9
10
public void startThreadUseSubClass() {
class MyThread extends Thread {
public void run() {
System.out.println("start thread using Subclass of Thread");
}
}
MyThread thread = new MyThread();
thread.start();
}

另一种是传递给 Thread 构造函数一个 Runnable 对象。

1
2
3
4
5
6
7
8
public void startThreadUseRunnalbe() {
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("start thread using runnable");
}
});
thread.start();
}

当然, Runnalbe 对象,也不是只有这一种形式,例如如果我们想要线程执行时返回一个值,就需要用到另一种 Runnalbe 对象,它
对原来的 Runnalbe 对象进行了包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void startFutureTask() {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
public Integer call() {
return 1;
}
});
new Thread(task).start();
try {
Integer result = task.get();
System.out.println("future result " + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

结束线程

wait 与 sleep

sleep 会使得当前线程休眠一段时间,但并不会释放已经得到的锁。

wait 会阻塞住,并释放已经得到的锁。一直到有人调用 notify 或者 notifyAll,它会重新尝试得到锁,然后再唤醒。

线程池

好处

  • 复用

线程池中有一系列线程,这些线程在执行完任务后,并不会被销毁,而会从任务队列中取出任务,执行这些任务。这样,就避免为每个任务
都创建线程,销毁线程。 在有大量短命线程的场景下,如果创建线程和销毁线程的时间比线程执行任务的时间还长,显然是不划算的,这时候,使用线程池就会有明显
的好处。

  • 流控

同时,可以设置线程数目,这样,线程不会增大到影响系统整体性能的程度。当任务太多时,可以在队列中排队,
如果有空闲线程,他们会从队列中取出任务执行。

使用

  • 线程数目

那么,线程的数目要设置成多少呢?这需要根据任务类型的不同来设置,假如是大量计算型的任务,他们不会阻塞,那么可以将线程数目设置
为处理器数目。而如果任务中涉及大量IO,有些线程会阻塞住,这样就要根据阻塞线程数目与运行线程数目的比例,以及处理器数目来设置
线程总数目。例如阻塞线程数目与运行线程数目之比为n, 处理器数目为p,那么可以设置 n * (p + 1) 个线程,保证有 n 个线程处于运行
状态。

  • Executors

JDK 的 java.util.concurrent.Executors 类提供了几个静态的方法,用于创建不同类型的线程池。

1
2
3
4
5
6
7
8
9
ExecutorService service = Executors.newFixedThreadPool(10);
ArrayList<Future<Integer>> results = new ArrayList<>();
for (int i = 0; i < 14; i++) {
Future<Integer> r = service.submit(new Callable<Integer>() {
public Integer call() {
return new Random().nextInt();
});
results.add(r);
}

newFixedThreadPool 可以创建固定数目的线程,一旦创建不会自动销毁线程,即便长期没有任务。除非显式关闭线程池。如果任务队列中有任务,就取出任务执行。

另外,还可以使用 newCachedThreadPool 方法创建一个不设定固定线程数目的线程池,它有一个特性,线程完成任务后,如果一分钟之内又有新任务,就会复用这个线程执行新任务。如果超过一分钟还没有任务执行,就会自动销毁。

另外,还提供了 newSingleThreadExecutor 创建有一个工作线程的线程池。

原理

JDK 中的线程池通过 HashSet 存储工作者线程,通过 BlockingQueue 来存储待处理任务。

通过核心工作者数目(corePoolSize) 和 最大工作者数目(maximumPoolSize) 来确定如何处理任务。如果当前工作者线程数目
小于核心工作者数目,则创建一个工作者线程执行这个任务。否则,将这个任务放入待处理队列。如果入队失败,再看看当前工作
者数目是不是小于最大工作者数目,如果小于,则创建工作者线程执行这个任务。否则,拒绝执行这个任务。

另外,如果待处理队列中没有任务要处理,并且工作者线程数目超过了核心工作者数目,那么,需要减少工作者线程数目。


静晴轩 ~ 晚晴幽草轩
个人微信公众号晚晴幽草轩;名字取自:“天意怜幽草,人间重晚晴”。
专注互联网开发(Web 大前端、快应用、小程序),以及分享优质网站、AI 应用、效率工具、心得感悟等内容。

文章目录
  1. 1. 如何创建一个线程
  2. 2. 结束线程
  3. 3. wait 与 sleep
  4. 4. 线程池
    1. 4.1. 好处
    2. 4.2. 使用
    3. 4.3. 原理