线程的4种创建方式及区别

本文详细介绍了Java中创建线程的四种方式:继承Thread类、实现Runnable接口、实现Callable接口以及使用线程池。重点讨论了线程池的优势、如何创建线程池、ExecutorService与ThreadPoolExecutor的使用,以及线程池参数设定和拒绝策略。文章还强调了避免使用Executors静态工厂创建线程池,推荐使用ThreadPoolExecutor,并提供了具体的创建示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

四种基本实现方式

1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
4、创建线程池

实现Runnable和实现Callable接口的方式基本相同,不过前者线程执行体run()方法无返回值,而后者执行call()方法有返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

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

2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

5、前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池

注:在前三种中一般推荐采用实现接口的方式来创建多线程

一、extends Thread类

通过继承Thread类来创建并启动多线程的一般步骤如下

1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

2】创建Thread子类的实例,也就是创建了线程对象

3】启动线程,即调用线程的start()方法

package com.example.worktest.async.thread.callable.service;

import org.springframework.stereotype.Service;

@Service
public class ThreadTest extends Thread{
   

    @Override
    public void run(){
   
        System.out.println("继承Thread类的线程");
    }
}

@GetMapping("/test1")
    public String threadTest() {
   
        ThreadTest threadTest = new ThreadTest();
        threadTest.start();
        return Thread.currentThread().getName();
    }

运行后的执行结果:
在这里插入图片描述

二、实现Runnable接口

通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】通过调用线程对象的start()方法来启动线程

package com.example.worktest.async.thread.callable.service;

import org.springframework.stereotype.Service;

@Service
public class RunTest implements Runnable{
   

    public void  run(){
   
        System.out.println("runnable___"+Thread.currentThread().getName());
    }
}
 @GetMapping("/test2")
    public String runnableTest() {
   
        RunTest runTest = new RunTest();
        Thread thread = new Thread(runTest);
        Thread thread1 = new Thread(runTest,"自己命名的runnable线程yyyy");
        thread.start();
        thread1.start();
        return Thread.currentThread().getName();
    }

在这里插入图片描述

三、实现Callable接口

3.1 Callable和Runnable接口的不同点

1】Runnable接口是run()方法,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

2】call()方法可以有返回值

3】call()方法可以声明抛出异常

4】Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值(下面以String为例)

3.2 实现步骤

1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

2】创建一个 FutureTask ,它将在运行时执行给定的 Callable ,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask/Future(线程池时使用) 对象的get()方法来获得子线程执行结束后的返回值
<< get方法建议与isDone方法同用!!不然get方法在线程执行完之前会一直阻塞!!>>

@Component
public class CallTest implements Callable<String>{
   

    @Override
    public String call() throws Exception{
   
        return "这是callable的call方法";
    }
}
@GetMapping("/test3")
    public void callableTest(){
   
        try{
   
            CallTest callTest = new CallTest();
            FutureTask<String> futureTask = new FutureTask<String>(callTest);
            Thread thread3 = new Thread(futureTask);
            thread3.start();
            while (true){
   
                if(futureTask.isDone()){
   
                    System.out.println(futureTask.get());
                    break;
                }
                Thread.sleep(2000);
                System.out.println("线程还未执行完,sleep2000ms后再尝试");
            }

        }catch (Exception e) {
   
            e.printStackTrace();
        }
    }

执行结果:
在这里插入图片描述

3.3 FutureTask

线程Thread的构造方法:
在这里插入图片描述

三个构造器:无参构造器、一个参数构造器和两个参数构造器。但是就没有我们Callable作为参数的构造器。那么,我们想要获取到线程,通过callable怎么获取呢 ?
在这里插入图片描述
我们先来看一下FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V>

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

public interface RunnableFuture<V> extends Runnable, Future<V> {
   
    void run();
}

可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
FutureTask提供了2个构造器:

public FutureTask(Callable callable) { }
public FutureTask(Runnable runnable, V result) { }

事实上,FutureTask是Future接口的一个唯一实现类。

四、线程池

4.0 线程池的好处

在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

减少资源的开销
减少了每次创建线程、销毁线程的开销。
提高响应速度 ,每次请求到来时,由于线程的创建已经完成,故可以直接执行任务,因此提高了响应速度。
提高线程的可管理性 ,线程是一种稀缺资源,若不加以限制,不仅会占用大量资源,而且会影响系统的稳定性。 因此,线程池可以对线程的创建与停止、线程数量等等因素加以控制,使得线程在一种可控的范围内运行,不仅能保证系统稳定运行,而且方便性能调优。

那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor

而我们创建时,一般使用它的子类:ThreadPoolExecutor

4.1 线程池不要使用Executors构造!!!

在这里插入图片描述
Executors 大大的简化了我们创建各种类型线程池的方式,为什么还不让使用呢?
其实,只要你打开看看它的静态方法参数就会明白了
在这里插入图片描述
传入的workQueue 是一个边界为 Integer.MAX_VALUE 队列,我们也可以变相的称之为无界队列了,因为边界太大了,这么大的等待队列也是非常消耗内存的

/**
 * Creates a {@code LinkedBlockingQueue} with a capacity of
 * {@link Integer#MAX_VALUE}.
 */
public LinkedBlockingQueue() {
   
  this(Integer.MAX_VALUE);
}

另外该 ThreadPoolExecutor方法使用的是默认拒绝策略(直接拒绝),但并不是所有业务场景都适合使用这个策略,当很重要的请求过来直接选择拒绝显然是不合适的
在这里插入图片描述
总的来说,使用 Executors 创建的线程池太过于理想化,

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值