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

注意:

  1. 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);
    }
}
posted @ 2023-08-27 21:17  Zebt  阅读(38)  评论(0)    收藏  举报