1.2 多线程 - 线程的创建

每个线程的作用是完成一定的任务,实际就是执行一段程序流(一段顺序执行的代码)。Java使用线程执行体来代表这段程序流。

继承 Thread 类创建线程类

创建并启动多线程的步骤如下:

  • 1、定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程需要完成的任务。因此把 run() 方法称为线程执行体
  • 2、创建 Thread 子类的实例,即创建了线程对象。
  • 3、调用线程对象的 start() 方法来启动该线程。

代码示例:

// 通过继承Thread类来创建线程类
public class FirstThread extends Thread { 
    private int i ;
    // 重写run方法,run方法的方法体就是线程执行体
    public void run() {
        for ( ; i < 100 ; i++ ){
            // 当线程类继承Thread类时,直接使用this即可获取当前线程
            // Thread对象的getName()返回当前该线程的名字
            // 因此可以直接调用getName()方法返回当前线程的名
            System.out.println(getName() +  " " + i);
        }
    }
    public static void main(String[] args){
        for (int i = 0; i < 100;  i++){
            // 调用Thread的currentThread方法获取当前线程
            System.out.println(Thread.currentThread().getName()
                +  " " + i);
            if (i == 20){
                // 创建、并启动第一条线程
                new FirstThread().start();
                // 创建、并启动第二条线程
                new FirstThread().start();
            }
        }
    }
}

Java 程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由 run() 方法确定的,而是由 main() 方法确定的 —— main() 方法的方法体代表主线程的线程执行体。

  • Thread.currentThread():currentThread() 是 Thread 类的静态方法,该方法总是返回当前正在执行的线程对象。
  • getName():该方法是 Thread 类的实例方法,该方法返回调用该方法的线程名字。
  • setName(String name):为线程设置名字,默认情况下:主线程的名字为main,用户启动的多个线程的名字依次为 Thread-0、Thread-1、Thread-n。

实现 Runnable 接口创建线程类

创建并启动多线程的步骤如下:

  • 1、定义 Runnable 接口的实现类,并重新该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
  • 2、创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
  • 3、调用线程对象的 start() 方法来启动该线程。

代码示例:

// 通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable {
    private int i ;
    // run方法同样是线程执行体
    public void run()   {
        for ( ; i < 100 ; i++ ) {
            // 当线程类实现Runnable接口时,
            // 如果想获取当前线程,只能用Thread.currentThread()方法。
            System.out.println(Thread.currentThread().getName()
                + "  " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100;  i++) {
            System.out.println(Thread.currentThread().getName()
                + "  " + i);
            if (i == 20) {
                SecondThread st = new SecondThread();     // ①
                // 通过new Thread(target , name)方法创建新线程
                new Thread(st , "新线程1").start();
                new Thread(st , "新线程2").start();
            }
        }
    }
}

Runnable 对象仅仅作为 Thread 对象的 target ,Runnable 实现类里包含的 run() 方法仅作为线程体。而实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执行其target 的 run() 方法。总结来说:FirstThread 直接创建的 Thread 子类即可代表线程对象;SecondThread 创建的 Runnable 对象只能作为线程对象的 target。
采用 Runnable 接口的方式创建的多个线程可以共享线程类的实例变量。

使用 Callable 和 Future 创建线程

Callable 接口提供了一个 call() 方法可以作为线程执行体:

  • call() 方法可以有返回值
  • call() 方法可以声明抛出异常

Callable 接口是Java5新增的接口,并不是 Runnable 接口的子接口,所以不能直接作为 Thread 的 target。call() 方法并不是直接调用,它是作为线程执行体被调用的。

Java5 提供了 Futurn 接口来代表 Callable 接口里 call() 的返回值,并为 Future 接口提供一个 FutureTask 实现类,该实现类实现了 Futurn 接口,并实现了 Runable 接口 —— 可以作为 Thread 类的 target 。

Futurn 接口里定义了如下方法来控制它关联的 Callable 任务

  • boolean cancel(boolean mayInterruptIfRunning):试图取消该 Future 里关联的Callable 任务。
  • V get():返回 Callable 任务里 call() 方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
  • V get(long timeout, TimeUnit unit):返回 Callable 任务里 call() 方法的返回值。该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常。
  • boolean isCancelled():如果在 Callable 任务正常完成前被取消,则返回 true 。
  • boolean isDone():如果 Callable 任务已完成,则返回 true 。

Callable 接口有泛型限制,Callable 接口里的泛型形参类型与 call() 方法返回值类型相同。

创建并启动有返回值的多线程的步骤如下:

  • 1、创建 Callable 接口的实现类,并实现 call() 方法将作为线程执行体,且该 call() 方法有返回值,再创建 Callable 实现类的实例。Java8 开始,可以直接使用 Lambda 表达式创建 Callable 对象。
  • 2、使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  • 3、使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  • 4、调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

代码示例:

public class ThirdThread{
    public static void main(String[] args){
        // 创建Callable对象
        ThirdThread rt = new ThirdThread();
        // 先使用Lambda表达式创建Callable<Integer>对象
        // 使用FutureTask来包装Callable对象
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
            int i = 0;
            for ( ; i < 100 ; i++ ){
                System.out.println(Thread.currentThread().getName()
                    + " 的循环变量i的值:" + i);
            }
            // call()方法可以有返回值
            return i;
        });
        for (int i = 0 ; i < 100 ; i++){
            System.out.println(Thread.currentThread().getName()
                + " 的循环变量i的值:" + i);
            if (i == 20){
                // 实质还是以Callable对象来创建、并启动线程
                new Thread(task , "有返回值的线程").start();
            }
        }
        try{
            // 获取线程返回值
            System.out.println("子线程的返回值:" + task.get());
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

创建线程的三种方式对比

  • 采用 Runnable、Callable 接口的方式创建多线程的优缺点:

    • 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
    • 这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
    • 劣势是,编程稍稍复杂, 如果需要访问当前线程,则必须使用 Thread.currentThread() 方法。采用继承 Thread 类的方式创建多线程的优缺点。
  • 采用继承 Thread 类的方式创建多线程的优缺点:
    - 劣势是:线程类已经继承了 Thread 类,所以不能再继承其他父类。
    - 优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1.1 多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念。 进程:进程指正在运行的程序。确切的来说...
    Pecksniff1994阅读 5,494评论 0 2
  • 1 多线程 1.1 多线程介绍   学习多线程之前,我们先要了解几个关于多线程有关的概念。  进程:进程指正在运行...
    圣堂刺客_x阅读 2,817评论 0 0
  • 先看几个概念:线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。多线程:解决多任务同时执行的需求,合理...
    yeying12321阅读 3,619评论 0 0
  • 1.多线程的概述 (1)线程、进程、程序的区别与联系?(来源,《疯狂Java讲义》P716) 程序:只是一个静态的...
    ql2012jz阅读 3,907评论 0 0
  • Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用三种方式来创...
    jalen2024阅读 4,449评论 0 1

友情链接更多精彩内容