导航
导航
文章目录
  1. 概述
  2. 一个线程的生命周期
  3. 线程的优先级
  4. 创建一个线程
    1. 通过实现 Runnable 接口
    2. 通过继承 Thread 类本身
    3. 通过 Callable 和 Future 创建线程
  5. 创建线程的三种方式对比
  6. 线程的几个主要概念
  7. 多线程的使用

Java 之多线程

概述

  • Java 给多线程编程提供了内置的支持。
  • 线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程不能独立存在,它必须是进程的一部分。
  • 进程:一个进程包括由操作系统分配的内存空间,包含 一个或多个 线程。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
  • 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

一个线程的生命周期

  • 新建状态:使用 new 关键字和 Thread 类或及其子类建立一个线程对象后,该线程对象就处于新建状态,直到执行 start()。
  • 就绪状态:调用 start() 之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,等待 JVM 里线程调度器的调度。
  • 运行状态:就绪状态的线程获取 CPU 资源,就可以执行 run(),之后线程处于运行状态。该状态的线程最为复杂,它可以变为阻塞状态、就绪状态或死亡状态。
  • 阻塞状态:如果线程执行了 sleep()、suspend() 等方法,失去所占资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后重新进入就绪状态。阻塞状态还可以分为三种:
    • 等待阻塞:运行中的线程执行 wait() 方法,进入等待阻塞状态;
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用);
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出 I/O 请求时,线程就进入阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或 I/O 处理完毕,线程重新进入就绪状态。
  • 死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时,线程就会切换到终止状态。

线程的优先级

  • 每个 Java 线程都有一个优先级,有助于操作系统确定调度顺序。
  • Java 线程的优先级是一个整数,取值范围是 1-10 (Thread.MIN_PRIORITY - MAX_PRIORITY)。
  • 每个 Java 线程默认分配一个优先级 5(NORM_PRIORITY)。
  • 具有优先级搞的线程应该对程序更重要,应该在优先级低的线程之前分配处理器资源。但是,线程的优先级不能保证线程执行的顺序,也非常依赖于平台。

创建一个线程

Java 提供了三种方法创建线程:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 Callable 和 Future 创建线程。

通过实现 Runnable 接口

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class RunnableDemo implements Runnable {
private Thread t;
private final String threadName;

RunnableDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName);
}

@Override
public void run() {
System.out.println("Running " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 线程睡眠
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + "interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}

public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}

public class ThreadTest_1 {
public static void main(String[] args) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();

RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
}
}

通过继承 Thread 类本身

本质上也是实现了 Runnable 接口的一个实例。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
class ThreadDemo extends Thread {
private Thread t;
private final String threadName;

ThreadDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName);
}

public void run() {
System.out.println("Running " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 线程睡眠
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + "interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}

public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}

public class ThreadTest_2 {
public static void main(String[] args) {
ThreadDemo T1 = new ThreadDemo("Thread-1");
T1.start();

ThreadDemo T2 = new ThreadDemo("Thread-2");
T2.start();
}
}

通过 Callable 和 Future 创建线程

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值;
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值;
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程;
  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
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
29
public class CallableThreadTest implements Callable<Integer> {
@Override
public Integer call() {
int i = 0;
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}

return i;
}

public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);

for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量 i 的值: " + i);
if (i == 5) {
new Thread(ft, "有返回值的线程").start();
}
}

try {
System.out.println("子线程的返回值: " + ft.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

创建线程的三种方式对比

  • 采用实现 Runnable、Callable 接口的方式创建多线程时,线程只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  • 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程的几个主要概念

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复

多线程的使用

有效利用多线程的关键是理解程序是 并发执行 而不是 串行执行 的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了

请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!