tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础与面向对象

  • 高级进阶

  • 并发合集

    • Java中创建线程的几种方式
      • 继承Thread类
      • 实现Runnable接口
      • Runnable比Thread好的地方
      • 实现Callable接口
      • 通过线程池创建线程
    • 并发相关概念与体系图
    • 线程状态与操作系统的用户态、内核态
    • 线程中的声明与守护线程_基础
    • 程序中的幽灵错误_基础
    • JDK并发包
    • 线程池相关
    • 并发中的安全集合
    • 生产者和消费者
    • 玩转单例模式
    • 一些工具类的原理
    • 并发包中的AQS
    • ThreadLocal与JMM
    • 锁的探究
  • JVM合集

  • 实战与细节

  • 代码之丑与提升

  • 《Java》学习笔记
  • 并发合集
EffectTang
2023-10-23
目录

Java中创建线程的几种方式

# Java中创建线程的几种方式

# 继承Thread类

public class Demo1 {

    public static void main(String[] args) {
        System.out.println("hello world");
        ATest aTest = new ATest();
        aTest.start();
    }
}

class ATest extends Thread{

    @Override
    public void run() {
        System.out.println("this is a new Thread");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

继承Thread类,并重写run()方法(run方法就是该线程要做的事情)。

最后使用该类调用start()即可开启一个线程。

Java中的类是单继承,但是类可以实现多个接口。

该种方法的弊端很明显,因为类是单继承,如果要想继承一些其他类就无法实现了。因此该种开启线程的方法很少用。

# 实现Runnable接口

public class Demo2 implements Runnable{

    @Override
    public void run() {
        System.out.println(1024);
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new Demo2());
        thread.start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

定义一个类,继承Runnable接口,并重写run()方法。

然后创建一个Thread对象(A),并使用实现Runnable接口的类创建对象,并将其作为参数传给Thread的构造方法。

最后 A 调用start()方法即可开启一个新的线程。

Thread th2 = new Thread(()-> System.out.println("this is lambda express"));
        th2.start();
1
2

当然在创建的时候,你还可以使用匿名内部类的方式进行创建,如果要执行的内容很少,还可以用lambda表达式进行简写。

# Runnable比Thread好的地方

下面我们来对刚才说的两种实现线程内容的方式进行对比,也就是为什么说实现 Runnable 接口比继承 Thread 类实现线程要好?好在哪里呢?

首先,我们从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。

第二点就是在某些情况下可以提高性能。

因为,使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,如果此时执行的内容比较少,比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。

如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Executing task in " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池

        for (int i = 0; i < 100; i++) {
            executor.submit(new MyRunnable());
        }

        executor.shutdown();
        while (!executor.isTerminated()) {
            // 等待所有任务完成
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

第三点好处在于 Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

  • 总结:解耦、更灵活、某些情况可能提高性能

# 实现Callable接口

它跟实现runnable接口类似,但有一个很大的不同点是:它可以获取线程执行完后的结果。

public class Demo3 implements Callable<String> {

    @Override
    public String call() {
        System.out.println("this is a new Thread");
        return "create";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new Demo3());
        Thread th = new Thread(futureTask);
        th.start();
        String result = futureTask.get();
        System.out.println(result);
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

使用实现callable<v>接口的方式来创建线程,需要配合FutureTask<v>来实现

  • 定义类,实现callable接口,并重写call()方法
  • 创建FutureTask的对象,并将目标类的对象作为参数传入构造方法
  • 然后创建Thread的对象,并用刚刚创建的FutureTask的对象作为参数传入构造方法
  • 最后调用start()方法,即可开启新的线程
  • 而返回值的获取:FutureTask的对象调用get()方法

# 通过线程池创建线程

public class Demo2 implements Runnable{

    @Override
    public void run() {
        System.out.println("this is a new Thread");
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new Demo2());

    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

首先定义一个线程池,我们这里定义数量为10,

接着我们将实现的Runnable 接口或者继承 Callable 接口传入,让线程池进行调用,从而实现新线程的开启

在上述例子中,是execute()方法帮忙开启了线程

关于通过线程池开启线程,实际上是不建议通过Executors来创建线程池,而是通过ThreadPoolExecutor来创建

因为,Executors可能会导致oom,此外还不能自定义线程的名称,不利于排查问题。

通过ThreadPoolExecutor创建线程池

  • 指定核心线程数、最大线程数、存活时间和工作队列
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 1000L;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, workQueue);
1
2
3
4
5
  • 将任务提交给线程池执行,使用execute()方法或submit()方法
threadPoolExecutor.execute(task); // 立即返回,不等待任务执行结果
1
  • 提交任务并返回一个Future对象,可以等待任务执行结果
Future<Result> future = threadPoolExecutor.submit(task); 
1
  • 关闭线程池,使用shutdown()方法或shutdownNow()方法
// 不再接受新任务,等待所有任务执行完毕后关闭线程池
threadPoolExecutor.shutdown(); 
// 尝试停止所有正在执行的任务,并返回等待执行的任务列表
threadPoolExecutor.shutdownNow(); 
1
2
3
4
上次更新: 2025/04/23, 16:23:16
Java中的引用
并发相关概念与体系图

← Java中的引用 并发相关概念与体系图→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式