【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 接口的实现方式

Runnablejava.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() 判断任务是否完成

注意

  1. cancel(boolean) 方法:该方法在任务已完成或已取消时无效果,出于某些原因无法取消任务时,方法也无效果;若任务在未开始时就调用 cancel 方法,则任务取消执行;若任务已开始,则 mayInterruptIfRunning 参数决定执行任务的线程(实现了解是什么任务时)是否会试图停止任务被打断;

    参数 mayInterruptIfRunning - true 表示执行任务的线程应当被打断(若实现了解线程);否则,执行中任务将会继续完成;

    方法在任务不能被取消时返回 false,通常由于任务已完成;否则返回 true;若两条或多条线程导致一个任务被取消,则至少其中一个返回 true;实现应当提供更强的保证;

    方法返回值并不能表示任务是否被取消,应当使用 isCancelled()

但这两个都属于接口,必须要有他们的实现类对象。可以让要多线程执行的类实现 Callable 接口,然后用 FutureTask 类对象管理该实现类对象的多线程执行结果,最后交由 Thread 开启线程。

FutureTask<V> 是一个位于 java.util.concurrent 的类,其实现接口 RunnableFuture<V> 继承了 RunnableFuture<V>

使用上述两种方式,可以获取多线程任务的结果,是作为前两种多线程实现方式的一种补充。

使用 CallableFuture 的实现方式过程较为繁琐,但可以分为以下几步:

  • 创建一个 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);
    }
}
posted @ 2023-08-27 21:17  Zebt  阅读(16)  评论(0)    收藏  举报