Java多线程-创建线程的多种方式


title: Java多线程_创建线程的多种方式
date: 2022-02-28 19:02:15
tags: Java


Java多线程_创建线程的多种方式

一、线程与进程

​ 几乎所有的操作系统都支持进程的概念,所有运行中的任务都对应一个进程(Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行中的程序,并且具有一定的独立功能,进程是系统进行资源调度的一个独立单位。

​ 一般而言,进程包含三个特征:独立性、动态性、并发性。

​ 一个程序运行后至少会有一个进程,一个进程可以包含多个线程,但至少要有个线程。

归纳起来就是说:操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。

温馨提示(并发性与并行性):

并发性(concurrency)和并行性(parallel)是两个概念,并行是指在同一时刻,有多条指令在多个处理器上同时执行;并发是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程被同时执行的效果。

二、多线程的优势

​ 线程在程序中是独立的、并发的执行流,与分隔的进程相比,线程分隔程度要小很多。当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源;但创建线程则简单很多,因此使用多线程来实现并发比使用多进程实现并发要强很多。

​ 1、进程之间不能共享内存,但线程之间共享内存非常容易,

​ 2、系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价要小很多,因此使用多线程实现多任务并发要比进程效率高很多。

​ 3、Java语言内置了多线程功能支持,而不是单纯的作为底层操作系统的调度模式,从而简化了Java的多线程编程。

三、线程的创建和启动

​ Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例,以下则介绍其中三种常见创建线程对象的方法。

1、继承Thread类创建线程类

​ 创建步骤:

​ ①定义Thread类的子类,并重写该类的run()方法,该run()方法体就代表了线程需要完成的任务。因此吧run()方法称为线程执行体。

​ ②创建Thread子类的实例,即创建了线程对象。

​ ③调用线程对象的start()方法来启动该线程。

/**
 * @program: code
 * @description: 通过继承Thread类创建线程对象
 * @author: yangzhihong
 * @create: 2022-02-28 20:29
 **/
public class Thread_jc extends Thread {
    private int i;
    //重写run()方法,run()方法的方法体就是线程执行体。
    public void run(){
        for (i = 0; i < 100; i++) {
            //d当线程类继承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 Thread_jc().start();
                //创建并启动第二个线程
                new Thread_jc().start();
            }
        }
    }
}

Thread类提供了常用的API

Thread.currentThread() : 静态方法用于获取当前的线程对象。

getName() : 可以获取线程对象的名称。

​ 主线程的名称默认是main,不能修改。

​ 子线程的名称默认是 Thread-0,…

​ 可以修改,修改方式,可以提供有参构造方法,调用父类的有参构造方法Thread(String name)

​ 也可以使用setName(String name) 设置线程名称。

2、实现Runnable接口创建线程

​ 自定义一个类实现Runnable接口,这个类是一个任务类,这个任务类需要重写Runnable接口中的任务方法,这个类的对象是一个任务对象,如果这个任务对象的任务需要在子线程中执行,必须依赖线程对象。

​ 创建步骤:

​ ①定义Runnable接口的实现类,并重写该方法的run()方法,该run()方法的方法体同样是该线程的线程执行体。

​ ②创建Runnable实现类的实例,并以此作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

​ ③调用线程线程对象的start()方法来启动线程。

​ **注意:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是Thread线程负责执行其target的run()方法。4

/**
 * @program: code
 * @description: 实现Runnable接口创建线程对象
 * @author: yangzhihong
 * @create: 2022-03-01 10:24
 **/
// 自定义一个任务类
class MyTask implements Runnable {

    @Override
    public void run() {
        for(int i=1; i<=100; i++) {
            System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + i + "个小怪");
        }
    }
}

public class RunnableThread {

    public static void main(String[] args) {

        // 创建任务对象
        MyTask myTask = new MyTask();

        // 创建线程对象
        Thread thread = new Thread(myTask, "子线程");
        // 开启子线程对象的任务
        thread.start();

        for(int j=1; j<=100; j++) {
            System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + j + "个大BOSS");
        }
    }

}

3、使用Callable和Future创建线程

​ 从Java5 开始,Java提供了Callable接口,该接口与Runnable接口非常相似,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大:可以有返回值、可以声明抛出异常。

​ Java5 提供了一个与Callable配套的Future接口,Future有个实现类FutureTask,该类的RunnableFuture接口对Future、Runnable都进行了实现。可以作为中介来关联Runnable与Future,并通过Future的公共方法V get() 获取到Callable的返回值,于是乎Callable对象就可以作为Thread的target来创建线程了。(V get()方法中的V是表示泛型,表示Callable接口里的类型必须与call()返回值类型相同)
在这里插入图片描述
​ 创建步骤:

​ ①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且该call()方法有返回值,再创建Callable实现类的实例对象。从Java8 开始可以直接使用Lambda表达式创建Callable对象。

​ ②使用FutureTask类包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

​ ③使用FutureTask对象作为Thread对象的target创建并启动新线程。

​ ④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @program: code
 * @description: 通过Callable与Future来创建线程对象
 * @author: yangzhihong
 * @create: 2022-03-01 11:56
 **/
// 实现了Callable接口的类,也是一个任务类
class MyCall implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {

        int sum = 0;

        for(int i=1; i<=100; i++) {
            sum += i;
        }

        return sum;
    }
}

public class CallableFutureThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 创建任务对象
        MyCall myCall = new MyCall();
        // 创建FutureTask对象这个对象实现了RunnableFuture接口
        // RunnableFuture多继承了Runnable接口和Future接口
        // 在构造FutureTask对象时,可以通过构造方法将Callable接口的
        // 实现对象传入
        FutureTask<Integer> futureTask = new FutureTask<>(myCall);
        // 创建一个线程对象
        Thread thread = new Thread(futureTask);
        thread.start();
        // 通过get()方法来获取Callable任务方法的返回结果
        if(futureTask.isDone()) {
            Integer result = futureTask.get();
            System.out.println(result);
        }

    }

}

4、这三种创建线程方式的对比

​ 通过继承Thread类和实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值和可以声明抛出异常,因此可以将Runnable和Callable归为同一种实现接口方式。

实现接口方式创建线程优缺点:

​ ①线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

​ ②在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源,从而可以将CPU、代码和数据分开。形成清晰的模型。较好的体现了面向对象的思想。

​ ③缺点,编程稍微复杂一些,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

采用继承Thread类方式创建线程优缺点:

​ ①缺点,因为线程类已经继承了Thread类,所以不能再继承其他父类。

​ ②优点,编写简单,如果需要访问当前线程,则不需要使用Thread.currentThread()方法,直接this即可获得当前线程。

因此一般推荐采用实现Runnable接口、Callable接口方式来创建多线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值