0%

线程你了解有多少?

线程的出生

众所周知,计算机可以“同时”运行多个进程来执行不同的任务(操作系统通过CPU时间片不断切换执行从而达到宏观上的同时运行。单核CPU在任意一个CPU时间片只会有一个进程在执行),那为什么还需要发明个线程呢?

  • 在多核 CPU 中,利用多线程可以实现真正意义上的并行执行
  • 在一个应用进程中也会存在多个同时执行的任务,若其中任意任务被阻塞,按原先的方式则会导致其他任务也会被阻塞。 通过对不同任务创建不同的线程去处理,可以提升程序处理的效率
  • 线程可以认为是轻量级的进程,所以线程的创建、销毁比进程的代价更小

线程的创建

继承Thread类

Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。
启动线程的唯一方法就是通过 Thread 类的 start()实例方法。
start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法

1
2
3
4
5
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}

实现Runnable/Callable

如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个Runnable 接口。

1
2
3
4
5
6
class MyThread extends OtherClass implements Runnable {
@Override
public void run() {
System.out.println("MyThread.run()");
}
}

线程池的创建

线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。
执行Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 创建多个有返回值的任务
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Callable<Integer> c = new MyCallable(i);
// 执行任务并获取 Future 对象
Future f = pool.submit(c);
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res: " + f.get().toString());
}
//.........
class MyCallable implements Callable<Integer> {
private int idx;
public MyCallable(int idx) {
this.idx = idx;
}
@Override
public Integer call() throws Exception {
return idx;
}
}

线程的生命周期

线程既然能够创建,那么也能被销毁,所以线程是有生命周期的。
线程一共有 6 种状态(NEW、 RUNNABLE、 BLOCKED、WAITING、 TIME_WAITING、 TERMINATED)

  • NEW-初始状态
    • 线程被构建此时还未调用start方法
  • RUNNABLED-运行状态
    • 即操作系统中的就绪运行两种状态
  • BLOCKED-阻塞状态
    • 线程因为某种原因放弃了CPU使用权进入等待状态,阻塞也分为几种情况
      • 等待阻塞
        • 运行的线程执行wait方法, jvm会把当前线程放入到等待队列
      • 同步阻塞
        • 运行的线程在获取对象的同步锁时,若该锁被占用了,则jvm会把当前线程放入到锁池中
      • 其他阻塞
        • 运行的线程执行Thread.sleep/join方法或者有I/O请求时,jvm会把当前线程设置为阻塞状态,当 sleep结束/join线程终止/io处理完毕则线程进入RUNNABLED
  • TIME_WAITING-超时等待状态
    • 超时以后自动返回
  • TERMINATED-终止状态
    • 表示当前线程执行完毕

线程的生命周期图例