【Java 多线程】5 - 1 多线程的实现
§5-1 多进程的实现
5-1.1 多线程的概念
要了解多线程,首先先要了解一些基础概念。
进程:进程是程序的基本执行实体。在 Windows 中,按下组合键 Ctrl + Shift + Esc
呼出任务管理器,在进程选项卡中列出的都是以一个个正在运行的程序为代表的进程。
线程:线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。线程可以简单理解为应用程序中相互独立,可以同时运行的功能。例如,在任务管理器中可以展开 Microsoft Edge 进程所含有的所有线程。
一个进程中含有多个线程,这就是多线程。多线程允许程序同时做多件事情,充分利用等待时间,提高效率。
多线程的应用场景很广,基本上想让多件事情同时运行时就用到多线程,比如软件中的耗时操作、所有的聊天软件、所有的服务器等。
并发:在同一时刻,有多个指令在单个 CPU 上交替执行。
并行:在同一时刻,有多个指令在多个 CPU 上同时执行。
5-1.2 多线程的实现方式
Java 提供了多种实现多线程的方式,下面将一一介绍这些实现方式。
5-1.2.1 继承 Thread
类的实现方式
Thread
位于 java.lang
,实现接口 Runnable
,表示一个程序的一个执行线程。JVM 允许应用程序并发运行多个执行线程。
创建一个执行线程的其中一种方式是,声明类继承 Thread
,并重写 run
方法。再通过 start
方法开启线程。
Thread
的构造器:
构造方法 | 描述 |
---|---|
Thread() |
分配一个新的线程对象 |
Thread(Runnable target) |
分配一个新的线程对象 |
Thread(Runnable target, String name) |
分配一个新的线程对象 |
方法:
方法 | 描述 |
---|---|
void run() |
若使用不同的 Runnable 运行对象创建线程,则调用该对象的 run 方法,否则方法不做任何事情 |
void start() |
开始执行线程,JVM 会调用该线程的 run 方法 |
void setName(String name) |
将线程名字更改为参数指定名称 |
String getName() |
获取线程名字 |
示例:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "Hello world");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//继承 Thread 的多线程实现方法
//获取线程
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1:");
t2.setName("线程2:");
//交替进行
t1.start();
t2.start();
}
}
运行,会看到两条线程交替执行。
注意:
- 一般不建议使用这种方式实现多线程,这种方式并不能很好地体现面向对象的设计模式,建议使用下方列出的
Runnable
实现方式; Runnable
是一个函数式接口,有且仅有一个方法run
,因此Thread
的构造器可以传入一个 Lambda 表达式;
5-1.2.2 实现 Runnable
接口的实现方式
Runnable
是 java.lang
的一个函数式接口,有且仅有一个抽象方法 run
。由线程执行的实例的类应当实现该接口。
若要启动线程,还需要通过 Thread
的构造器创建一个线程对象,并通过该对象调用 start
方法启动线程。
抽象方法:
方法 | 描述 |
---|---|
void run() |
用实现类对象创建线程时,执行线程会调用该对象的 call 方法 |
示例:
public class class MyThreadTwo implements Runnable {
@Override
public void run() {
Thread current = Thread.currentThread(); //返回当前执行线程的引用
for (int i = 0; i < 100; i++) {
System.out.println(current.getName() + "Hello world");
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//通过实现 Runnable 接口实现
//还需要通过 Thread::start 启动
MyThreadTwo t = new MyThreadTwo();
Thread thread = new Thread(t, "线程1:");
Thread thread2 = new Thread(t, "线程2:");
thread.start();
thread2.start();
}
}
运行,会看到两条线程交替执行。
注意:建议使用这种实现方式,这种方式更能体现面向对象的设计模式,代码更清晰、更易于维护。且这种方式可以重用一个 Runnable
对象,提高代码重用性。
5-1.2.3 使用 Callable
接口和 Future
接口实现
Callable
简介:
Callable
是在 java.util.concurrent
的一个函数式接口,有且仅有一个抽象方法 call
。实现类应当定义一个无参数方法 call
。
Callable
接口类似于 Runnable
,二者都用于可能由其他线程执行的类实例。但 Runnable
不返回结果,也无法抛出受查异常,
Callable
抽象方法:Callable
是一个泛型接口,具有唯一一个泛型参数 V
方法 | 描述 |
---|---|
V call() |
计算结果,或在无法完成时抛出异常 |
Future
简介:
Future
是在 java.util.concurrent
的一个接口,表示一个异步计算的结果。
接口提供了检查计算是否完成、等待完成、获取计算结果的方法。结果只能够在计算完成时通过 get
获取,或必要时阻塞至就绪。cancel
方法用于取消,还提供了其他的方法,用于判断任务是否完成或取消。一旦计算完成,计算就无法被取消。
若只是为了可取消的性质而不考虑返回一个可用结果,可考虑使用 Future<?>
,也可以基于底层执行的任务返回 null
。
Future<V>
抽象方法:Future
是一个泛型接口,具有唯一一个泛型参数 V
方法 | 描述 |
---|---|
boolean cancel(boolean mayInterruptIfRunning) |
尝试取消任务执行 |
V get() |
必要时等待计算完成,然后获取结果 |
V get(long timeout, TimeUnit unit) |
必要时等待计算在指定时间内完成,若结果可用则返回结果 |
boolean isCancelled() |
判断当前任务是否在正常完成前已被取消 |
boolean isDone() |
判断任务是否完成 |
注意:
-
cancel(boolean)
方法:该方法在任务已完成或已取消时无效果,出于某些原因无法取消任务时,方法也无效果;若任务在未开始时就调用cancel
方法,则任务取消执行;若任务已开始,则mayInterruptIfRunning
参数决定执行任务的线程(实现了解是什么任务时)是否会试图停止任务被打断;参数
mayInterruptIfRunning
-true
表示执行任务的线程应当被打断(若实现了解线程);否则,执行中任务将会继续完成;方法在任务不能被取消时返回
false
,通常由于任务已完成;否则返回true
;若两条或多条线程导致一个任务被取消,则至少其中一个返回true
;实现应当提供更强的保证;方法返回值并不能表示任务是否被取消,应当使用
isCancelled()
;
但这两个都属于接口,必须要有他们的实现类对象。可以让要多线程执行的类实现 Callable
接口,然后用 FutureTask
类对象管理该实现类对象的多线程执行结果,最后交由 Thread
开启线程。
FutureTask<V>
是一个位于 java.util.concurrent
的类,其实现接口 RunnableFuture<V>
继承了 Runnable
和 Future<V>
。
使用上述两种方式,可以获取多线程任务的结果,是作为前两种多线程实现方式的一种补充。
使用 Callable
和 Future
的实现方式过程较为繁琐,但可以分为以下几步:
- 创建一个 Java Bean,实现
Callable
接口,并重写抽象方法call
(具有返回值); - 创建
Callable
接口的实现类对象(表示多线程要完成的任务); - 创建
FutureTask
对象(管理多线程运行结果,该类是Future
的实现类); - 创建
Thread
类对象(传入FutureTask
对象),并启动线程;
示例:
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int res = 0;
for (int i = 0; i <= 100; i++) {
res += i;
}
return res;
}
}
public class ThreadDemo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用 Callable 和 Future 接口
// Callable 具有唯一方法 call,有返回值,表示任务执行结果
// FutureTask 是 Future 实现类,用于管理多线程结果
// 创建 Thread 对象,开启线程
MyCallable work = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(work);
Thread t1 = new Thread(task);
t1.start();
int res = task.get();
System.out.println(res);
}
}