Java多线程:线程的创建与启动

简介: 线程的创建与启动

面试题:

  • Java中创建线程有几种方式。
  • 不同的创建方式有什么区别。
  • 如何启动一个线程。

Java中创建线程的方式

  • JDK源码中的描述:两种

image.png

  • 第一种是继承Thread类,重写其run()方法()。
  • 第二种是实现Runnable接口,重写run()方法,再将Runnable实例传给Thread,Thread类最终会调用target.run()(target即为Runnable实例)方法来执行。
    image.png
  • 代码演示
/**
 * 通过继承Thread类,重写run()方法实现的线程
 *
 * @author futao
 * @date 2020/6/4
 */
public class ByThread extends Thread {

    @Override
    public void run() {
        System.out.println("通过继承Thread实现的多线程:" + Thread.currentThread().getName());
    }

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


/**
 * 通过实现Runnable接口,重写run()方法实现的线程
 *
 * @author futao
 * @date 2020/6/4
 */
public class ByRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("通过实现接口Runnable实现的多线程:" + Thread.currentThread().getName());
    }

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

核心思想

从源代码的角度来看,创建线程的方式只有一种,唯一的途径就是实例化一个Thread对象,通过thread.start()来启动线程。而继承Thread类和实现Runnable接口,只不过是对方法执行单元(run()方法)的两种不同实现。

两种方式的对比:

  1. 从代码架构角度:使用Runnable接口得我方式可以将线程的创建/停止/状态管理等与真正的业务逻辑解耦,使Runnable子类只需要关注真正的业务即可。
  2. 从性能损耗角度:使用继承Thread的方法,每次执行任务都需要启动一个新的线程,创建一个新的Thread实例,任务执行完毕之后还需要进行销毁。对性能的损耗比较严重。而实现Runnable接口的方式,可以实现对线程的复用,每次给线程传递不同的任务(Runnable实例)即可,不需要频繁的创建与销毁线程。(参考线程池的执行过程
  3. 从可扩展性角度:Java只支持单继承,所以实现Runnable接口的方式更好,避免继承的局限,方便后续对程序进行扩展。

Q: 如果同时继承Thread类和实现Runnable接口,会发生什么?

/**
 * @author futao
 * @date 2020/6/4
 */
public class Both {

    public static void main(String[] args) {
        new Thread(() -> {
            //通过lambda表达式创建Runnable子类对象
            System.out.println("来自实现Runnable接口的run()方法");
        }) {
            //Thread的匿名内部类,直接重写Thread父类的run()方法
            @Override
            public void run() {
                System.out.println("来自重写Thread类的run()方法");
            }
        }.start();
    }
}

image.png

  • 从结果可以看到,最终线程执行的是匿名内部类的run()方法。原因是在我们将Thread的start()方法重写之后不会再执行调用Runnable.run()方法。而执行我们重写之后的run()方法。
  @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

错误观点

  • 线程池: 通过线程工厂ThreadFactory.newThread()创建线程,而ThreadFactory.newThread()方法中也是通过实例化Thread对象的方式创建线程。
       ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r);
            }
        });
  • 通过FutureTask<>Callable: FutureTask实际上实现了Runnable接口,并且在重写的run()方法中调用了传递进来的Callable对象的call()方法。所以这种方式只不过是对Runnable方式的一种封装而已。本质上也只是实现线程执行单元的一种方法,最终需要将FutureTask对象传入Thread()对象进行执行。
/**
 * @author futao
 * @date 2020/6/6.
 */
public class CallableFutureTask {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("666");
                Thread.sleep(3000L);
                return Thread.currentThread().getName();
            }
        });
        new Thread(task).start();
        //将会阻塞,直到线程任务执行完毕
        System.out.println(task.get());
    }
}

image.png
image.png

线程的启动

  • 调用Thread对象的start()方法。三个过程:

    1. 判断线程状态。
    2. 加入线程组。
    3. 执行native方法start0()
  • 直接调用run()方法:只是普通方法调用,不会开启新的线程。
  • start()方法只能被调用一次,如果第二次调用,将抛出异常,即启动过程的第一步:检查线程状态不通过。

本文源代码

image.png

image.png

目录
相关文章
|
1天前
|
缓存 Java
Java并发编程:深入理解线程池
【4月更文挑战第26天】在Java中,线程池是一种重要的并发工具,它可以有效地管理和控制线程的执行。本文将深入探讨线程池的工作原理,以及如何使用Java的Executor框架来创建和管理线程池。我们将看到线程池如何提高性能,减少资源消耗,并提供更好的线程管理。
|
1天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
1天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
2天前
|
安全 算法 Java
JavaSE&多线程&线程池
JavaSE&多线程&线程池
16 7
|
2天前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
6 0
|
3天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
3天前
|
存储 缓存 NoSQL
为什么Redis使用单线程 性能会优于多线程?
在计算机领域,性能一直都是一个关键的话题。无论是应用开发还是系统优化,我们都需要关注如何在有限的资源下,实现最大程度的性能提升。Redis,作为一款高性能的开源内存数据库,因其出色的单线程性能而备受瞩目。那么,为什么Redis使用单线程性能会优于多线程呢?
15 1
|
3天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
16 0
|
3天前
|
监控 安全 Java
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
11 2
|
3天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
29 1

热门文章

最新文章