多线程-01-实现多线程的三种方式

本文章已经生成可运行项目,

一、实现多线程的三种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

实现多线程的方式一:继承Thread类

  1. 继承Thread类可以理解为:把【线程】和【任务】(要执行的代码)合并在了一起
  2. Thread类两个重要方法:
方法名说明
void run()在线程开启后,此方法将被调用执行
void start()start方法才是启动线程的方法,调用此方法使当前线程开始执行,Java虚拟机会调用run方法()
  1. 继承Thread类实现多线程的步骤:

    • 自定义线程类的创建:
      • 第一步:自定义一个类(MyThread)继承Thread类
      • 第二步:在自定义类(MyThread)中重写Thread的run方法
    • 自定义线程类的使用:(写一个测试类)
      • 第三步(使用自己创建的线程类):创建线程对象
      • 第四步:调用start方法开启线程
  2. 代码示范:

    • 1、自定义Thread类:
public class MyThread extends Thread {
    @Override
    public void run() {
    // run方法里面实现需要的代码逻辑
        for(int i=0; i<100; i++) {
            System.out.println(i);
        }
    }
}
  • 2、写一个测试类开启线程:
    • 注意:main线程是程序启动时自动创建的第一个线程,我们写的很多代码都是在main线程中执行的
public class MyThreadDemo {
    public static void main(String[] args) {
    // 这里其实有个main线程
    
    //创建两个线程
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();
        
        //void start() 调用此方法使此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

问题1: 为什么要重写run()方法?

因为run()是用来封装被线程执行的代码

问题2:run()方法和start()方法的区别?

  • run():封装线程执行的代码,直接调用,相当于普通方法的调用
  • start():启动线程,然后由JVM调用此线程的run()方法

实现多线程的方式二:实现Runnable接口

  1. 可以理解为把【线程】和【任务】(要执行的代码)分开
    • Runnable 可运行的任务(线程要执行的代码)
    • Thread 代表线程(把Runnable作为参数传进来)
  • Thread构造方法

    方法名说明
    Thread(Runnable target)分配一个新的Thread对象,传一个runnable对象进去
    Thread(Runnable target, String name)分配一个新的Thread对象,传一个runnable对象以及线程的名称
  1. 通过实现Runnable接口实现多线程的步骤:

    • 自定义线程类的创建:
      • 第一步:自定义一个类(MyRunnable)实现Runnable接口
      • 第二步:在自定义类(MyRunnable)中重写Runnable接口的run方法
    • 自定义线程类的使用:(写一个测试类)
      • 第三步:创建自定义线程类对象
      • 第四步:创建线程类对象,并把自定义线程类对象作为构造参数
      • 第五步:调用start方法启动线程
  2. 代码示范:

    • 1、自定义线程类
public class MyRunnable implements Runnable {
    @Override
    public void run() {
    // run方法中实现代码逻辑
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

线程的构造函数的理解——Thread(Runnable target, String name)

  • 构造函数public Thread(Runnable target, String name) 的理解:它是Thread类的一个公开构造函数,它接受两个参数:一个Runnable类型的target和一个String类型的name。

    • Runnable是一个接口:其中定义了一个无返回值、无参数的run方法。任何实现了Runnable接口的类都必须提供这个run方法的实现。
    • target参数的作用:当线程启动时(即调用线程的start方法时):
      • target参数不为null:那么线程会调用target对象的run方法。这意味着你可以将任何实现了Runnable接口的对象的实例传递给Thread的构造函数,然后当线程启动时,该Runnable接口的对象的run方法会被执行。
      • target为null:如果在创建Thread对象时,target参数被设置为null,那么当线程启动时,线程会调用自己的run方法(即Thread类中的run方法)。默认情况下,Thread类中的run方法不执行任何操作(它是一个空实现)。因此,如果你希望线程执行特定的任务,你通常会创建一个实现了Runnable接口的类,然后将其实例传递给Thread的构造函数。
    • name参数的作用:name参数用于为线程设置一个名称。这有助于在调试和日志记录时识别线程。线程的名称可以通过调用线程的getName方法来获取。
  • 2、编写测试类

public class MyRunnableDemo {
    public static void main(String[] args) {
    
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();


        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
        
        //Thread(Runnable target)
//        Thread t1 = new Thread(my);
//        Thread t2 = new Thread(my);

        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"线程1");
        Thread t2 = new Thread(my,"线程2");

        //启动线程
        t1.start();
        t2.start();
    }
}

实现多线程的方式三:实现Callable接口

  1. 这种方式实现多线程其实是方式二的优化,它可以获取方法的返回值。(FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况)
  • 方法介绍

    方法名说明
    V call()计算结果,如果无法计算结果,则抛出一个异常(V表示返回call方法返回值的类型)
    FutureTask(Callable callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
    V get()如有必要,等待计算完成,然后获取其结果(V表示返回call方法返回值的类型)
  1. 通过实现Callable接口实现多线程的实现步骤

    • 自定义线程类的实现:
      • 第一步:自定义一个类(MyCallable)实现Callable接口
      • 第二步:在自定义类(MyCallable)中重写call()方法
    • 自定义线程类的使用:(写一个测试类)
      • 创建自定义类(MyCallable)的对象
      • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
      • 创建Thread类的对象,把FutureTask对象作为构造方法的参数
      • 调用start方法(启动线程)
      • 再调用get方法,就可以获取线程结束之后的结果(不一定要获取结果,可选)
  2. 代码实现

    • 1、自定义线程类
// Callable<String>这里的泛型对应call方法的返回值
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}
  • 2、编写测试类
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       // 1、创建自定义线程类
        MyCallable mc = new MyCallable(); //线程开启之后需要执行里面的call方法

        //2、创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
        FutureTask<String> ft = new FutureTask<>(mc);

        //3、创建Thread类的对象,把FutureTask对象作为构造方法的参数
        Thread t1 = new Thread(ft);
		
		 // 获取线程结束之后的结果
        String s = ft.get();
        //开启线程
        t1.start();

        //String s = ft.get();
        System.out.println(s);
    }
}

三种实现多线程方式的对比

  • 实现Runnable、Callable接口
    • 好处: 扩展性强,实现该接口的同时还可以继承其他的类,可以共享一个 target 对象(即Runnable或者是Callable接口对象),所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
    • 缺点: 编程相对复杂,要额外创建对象(Runnable或者是Callable对象),不能直接使用Thread类中的方法,需要使用Thread.currentThread()方法调用
  • 继承Thread类
    • 好处: 编程比较简单,可以直接使用Thread类中的方法
    • 缺点: 可以扩展性较差,不能再继承其他的类
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值