创建线程的四种方法,以及它们的优缺点

本文详细介绍了四种创建Java线程的方法:继承Thread类、实现Runnable接口、实现Callable接口及使用线程池。通过实例展示了每种方法的具体应用,包括线程的启动、运行和结果获取,以及线程池的创建与任务分配。

1.用继承Thread类,重写它的run()方法。

在这里插入图片描述
我们可以从图中发现Thread类实现了Runnable接口并且重写了run()方法。而创建线程的第一种方式就是继承Thread类并重写它的run()方法。但是这样做就有缺点,你继承Thread类之后就不能继承其它的类了,不易后期的维护。

我就以计算1-100之内数之和为例:

public class TestThread {
    public static void main(String[] args) {
        new MyThread().start();
    }

}
class MyThread extends  Thread{
    @Override
    public void run() {
        int sum=0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        System.out.println("sum="+sum);
    }
}

2.实现Runnable接口,重写run()方法。

这个方法较之前的方法要好一点,但是也有缺点,就是run()方法不能有返回值和抛出异常。

public class TestRunnable {
    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }

}
class MyRunnable implements   Runnable{
    @Override
    public void run() {
        int sum=0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        System.out.println("sum="+sum);
    }
}

3.实现Callable接口,重写call()方法。

相较于Runnable接口的方式,方法可以有返回值,并且可以抛出异常。但是此方式需要FutureTask类支持来获取run()方法的返回值。
在这里插入图片描述
从图中我们可以看到,FutureTask类实现了RunnableFuture接口,而RunnableFuture继承了Future接口和Runnable接口。

public class TCallable {
    public static void main(String[] args) {
       MyCallable callable = new MyCallable();
       FutureTask<Integer> futureTask=new FutureTask<>(callable);
       new Thread(futureTask).start();
        try {
        //需要注意的一点是,在得到结果之前,Future接口的get()方法会一直阻塞和ServerSocket类的accept()方法一样
            int sum=futureTask.get();
            System.out.println("sum="+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }

}
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        return  sum;
    }
}

4.使用线程池的方式来创建线程。

1.线程池:提供了一个线程队列,队列中保存者所有等待状态的线程, 避免了创建与销毁额外开销,提高了相应速度。
在这里插入图片描述

二、线程池的体系结构:
java.util.concurrent.Executor:负责线程的使用与调度的共接口
|–**ExecutorService 子接口:线程池的主要接口
|–ThreadPoolExecutor 线程池的实现类
|–ScheduleExecutorService 子接口:负责线程的调度
|–ScheduleThreadPoolExecutor:继承ThreadPoolExecutor,
实现ScheduleExecutorService

三、工具类 :Executors
ExecutorService newFixedThreadPool():创建固定大小的线程池
ExecutorService newFixedThreadPool():缓存线程池,线程池的大小数量不固定,可以根据需求自动的更改数量
ExecutorService newSingleThreadExecutor():创建单个线程,线程池中只有一个出现线程
ScheduleExecutorService newScheduleThreadPool():创建固定大小的线程,可以延迟或定时的执行任务

public class TestThreadPool {
    public static void main(String[] args) {
        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        MyThreadPool myThreadPool = new MyThreadPool();
        //2.线程池中分配任务
        Future<Integer> future = pool.submit(myThreadPool);
        try {
            int sum=future.get();
            System.out.println("sum="+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            //3.关闭线程池
            pool.shutdown();
        }
    }
}
class MyThreadPool implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        return  sum;
    }
}
创建线程常见方式有四种,分别是继承 `Thread` 、实现 `Runnable` 接口、使用 `Callable` 和 `FutureTask`、使用线程池,以下简述各自的优缺点: ### 继承 `Thread` 创建线程 - **优点**: - **灵活性**:可直接使用 `Thread` 方法,在 `Thread` 基础上进行扩展,可对线程进行更灵活的控制[^1]。 - **控制重用性**:在某些场景下,继承 `Thread` 可以更方便地对线程行为进行定制和管理,提高代码的可重用性[^1]。 - **缺点**: - **大量代码复杂性**:由于 Java 是单继承的,若一个继承了 `Thread` ,就不能再继承其他,可能导致代码的设计和维护变得复杂,尤其是在需要继承其他来实现功能时会受到限制[^1]。 示例代码: ```java class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " 运行,i = " + i); } } } public class ThreadTest1 { public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); } } ``` ### 实现 `Runnable` 接口创建线程 - **优点**: - **简单性**:实现 `Runnable` 接口比继承 `Thread` 更简单,只需要实现一个 `run()` 方法即可。 - **避免继承的问题**:由于 Java 支持实现多个接口,所以实现 `Runnable` 接口的还可以继承其他,避免了单继承的局限性。 - **更好地测试和维护**:多个线程可以共享同一个 `target` 对象,适合多个相同线程处理同一份资源的情况,能将 CPU、代码和数据分开,形成清晰的模型,体现面向对象思想,便于代码的测试和维护[^1][^2]。 - **缺点**: - **没有 `Thread` 的默认行为**:`Runnable` 接口中只有一个 `run()` 方法,没有 `Thread` 的其他默认行为,如线程的优先级、线程组等,需要手动进行设置。 - **未定义的优先级**:线程的优先级没有明确的定义,需要在创建线程时手动设置。 示例代码: ```java class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " 运行,i = " + i); } } } public class ThreadTest2 { public static void main(String[] args) { MyRunnable target = new MyRunnable(); Thread t1 = new Thread(target); t1.start(); } } ``` ### 使用 `Callable` 和 `FutureTask` 创建线程 - **优点**: - **支持返回值**:`Callable` 接口的 `call()` 方法可以有返回值,通过 `FutureTask` 可以获取线程执行的结果。 - **支持异常处理**:`call()` 方法可以抛出异常,方便进行异常处理。 - **更好地协作**:可以与线程池结合使用,实现更复杂的多线程协作。 - **更灵活的线程池操作**:可以将 `FutureTask` 提交给线程池执行,方便对线程池进行管理。 - **更好的资源管理**:可以根据需要获取线程的执行结果,避免资源的浪费。 - **缺点**: - **增加了复杂性**:相比于实现 `Runnable` 接口,使用 `Callable` 和 `FutureTask` 需要更多的步骤和代码,增加了代码的复杂性。 - **需要更多的语法**:需要使用 `FutureTask` 来包装 `Callable` 对象,并且需要调用 `get()` 方法来获取线程的执行结果,增加了语法的复杂度。 示例代码: ```java import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 10; i++) { sum += i; } return sum; } } public class ThreadTest3 { public static void main(String[] args) { MyCallable callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); Thread t1 = new Thread(futureTask); t1.start(); try { System.out.println("线程执行结果:" + futureTask.get()); } catch (Exception e) { e.printStackTrace(); } } } ``` ### 使用线程池创建线程 - **优点**: - **充分利用系统资源**:线程池可以重复使用线程,避免了频繁创建和销毁线程带来的性能开销,充分利用了系统资源。 - **提升性能**:线程池可以根据系统的性能和负载情况,合理地调整线程的数量,提高了多线程的执行效率。 - **更好地调试**:线程池可以对线程进行统一的管理和监控,方便进行调试和优化。 - **更容易扩展**:可以根据需要动态地调整线程池的大小和参数,适应不同的应用场景。 - **缺点**: - **增加了复杂性**:线程池的配置和管理需要一定的专业知识,增加了代码的复杂性。 - **需要更多的语法**:使用线程池需要使用 `Executor` 框架,需要了解相关的方法,增加了语法的复杂度。 - **需要考虑线程池大小**:线程池的大小需要根据系统的性能和负载情况进行合理的配置,否则可能会导致性能下降或资源浪费。 示例代码: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class MyTask implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " 运行"); } } public class ThreadTest4 { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); for (int i = 0; i < 5; i++) { executorService.submit(new MyTask()); } executorService.shutdown(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值