Java 创建线程的方式

Java层面 创建线程

方法一:继承Thread类

java 复制代码
package com.evan.juc.base.creadThread;


class ShareData extends Thread {
    @Override
    public void run() {
        System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
    }
}

public class MyThread {
    public static void main(String[] args) {
        // 写法一
        Thread thread1 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
                    }

                }
        );

        // 写法二
        Thread thread2 = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
                    }

                }
        );

        // 写法三
        Thread thread3 = new ShareData();

        thread1.start();
        thread2.start();
        thread3.start();
    }

}

方法二:实现Runnable接口

java 复制代码
package com.thread.basic.creadThread;


/**
 * 实现Runnable接口,并重写run方法,在run方法中打印当前线程的名字
 *
 */
class ShareDataOfRunnalbe implements Runnable {

    @Override
    public void run() {
        System.out.println("当前线程/创建出的线程为" + Thread.currentThread().getName());
    }
}


public class MyRunnable {

    public static void main(String[] args) {
        ShareDataOfRunnalbe shareDataOfRunnalbe = new ShareDataOfRunnalbe();
        Thread thread1 = new Thread(shareDataOfRunnalbe);
        Thread thread2 = new Thread(shareDataOfRunnalbe);

        thread1.start();
        thread2.start();
    }
}

通过实现 Runnable 接口实现多线程,如代码所示,首先通过 ShareDataOfRunnalbe 类实现 Runnable 接口,然后重写 run() 方法,之后只需要把这个实现了 run() 方法的实例传到 Thread 类中就可以实现多线程。

方法三:线程池创建线程

csharp 复制代码
public class TestCreatThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 
        10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
        
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.submit(() -> {
                            System.out.println(Thread.currentThread().getName() + "线程 starting ....");
                            try {
                                TimeUnit.SECONDS.sleep(2);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + "线程 end....");
                        }
                );
            }
        } finally {
            threadPool.shutdown();
        }
    }
}

来看看线程池是怎么实现线程的?

scss 复制代码
static class DefaultThreadFactory implements ThreadFactory {

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r, 
                              namePrefix + threadNumber.getAndIncrement(),0);

        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。

在面试中,如果你只是知道这种方式可以创建线程但不了解其背后的实现原理,就会在面试的过程中举步维艰,想更好的表现自己却给自己挖了"坑"。

所以我们在回答线程实现的问题时,描述完前两种方式,可以进一步引申说"我还知道线程池和Callable 也是可以创建线程的,但是它们本质上也是通过前两种基本方式实现的线程创建。"这样的回答会成为面试中的加分项。然后面试官大概率会追问线程池的构成及原理,这部分内容会在后面的课时中详细分析。

方式四:实现Callable接口

csharp 复制代码
class CallableDemo implements Callable<Integer> {

    //和runnable的接口比起来,这是有返回值的接口
    //还可以抛异常
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "****进入callable了!");
        Thread.sleep(3000);
        return 1024;
    }
}

public class CallableTest {
    public static void main(String[] args) throws Exception {
        //把实现callable的接口当做参数传入futuretask
        FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
        //因为futureTask实现了Runnable接口,像普通的Runnable实现类一样传入Thread就可以了
        Thread t1 = new Thread(futureTask, "t1");
        //正常启动
        t1.start();
        //尝试获取返回结果
        System.out.println("****** result=" + futureTask.get());
    }
}
ini 复制代码
t1****进入callable了!
****** result=1024
csharp 复制代码
  //把实现callable的接口当做参数传入futuretask
  FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
  //因为futureTask实现了Runnable接口,像普通的Runnable实现类一样传入Thread就可以了
  Thread t1 = new Thread(futureTask, "t1");
  //让两个相同的线程同时执行futureTask
  Thread t2 = new Thread(futureTask, "t2");
  //正常启动
  t1.start();
  t2.start();
  //尝试获取返回结果
  System.out.println("****** result=" + futureTask.get()); 
ini 复制代码
t1****进入callable了!
****** result=1024

看,还是和刚才一样,也就是说一个FutureTask只能绑定一个Thread!绑定多了也是无效的!

java 复制代码
package com.thread.basic.creadThread;

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

public class CallableTest02 {

    public static void main(String[] args) throws Exception {
        // 创建一个 `ExecutorService`
        ExecutorService executor = Executors.newSingleThreadExecutor();

        // 创建一个 `Callable` 任务
        Callable<Integer> task = () -> {
            // 执行异步任务
            int sum = 0;
            for (int i = 1; i <= 100; i++) {
                sum += i;
            }

            // 返回结果
            return sum;
        };

        // 提交任务并获取结果
        Future<Integer> future = executor.submit(task);
        Integer result = future.get();

        // 打印结果
        System.out.println("结果: " + result);

        // 关闭 `ExecutorService`
        executor.shutdown();
    }
}

通过有返回值的 Callable 创建线程,Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数。

无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,如代码所示, submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。

JVM层面实现线程

在JVM 层面实现线程只有一种方式就是通过Thread类创建线程

创建Java线程主要有两种方式,但实质上都是通过Thread类实现:

  1. 实现Runnable接口 :这种方式中,你定义一个类实现Runnable接口,并重写run()方法来包含任务代码。然后,将这个Runnable实例传递给Thread类的构造器,通过Thread对象的start()方法启动线程,执行run()方法里的代码。
  2. 继承Thread :直接继承Thread类并重写其run()方法,将任务代码放在重写的run()方法中。通过创建该子类的实例并调用其start()方法来启动线程。

这两种方式的核心都是通过Thread类来创建线程,不同之处在于如何提供线程执行的代码------是通过外部Runnable对象还是直接在Thread子类中定义。

相关推荐
coderWangbuer21 分钟前
基于springboot的高校招生系统(含源码+sql+视频导入教程+文档+PPT)
spring boot·后端·sql
攸攸太上26 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
Kenny.志29 分钟前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端
sky丶Mamba1 小时前
Spring Boot中获取application.yml中属性的几种方式
java·spring boot·后端
千里码aicood2 小时前
【2025】springboot教学评价管理系统(源码+文档+调试+答疑)
java·spring boot·后端·教学管理系统
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
liuxin334455663 小时前
教育技术革新:SpringBoot在线教育系统开发
数据库·spring boot·后端
数字扫地僧3 小时前
HBase与Hive、Spark的集成应用案例
后端
架构师吕师傅3 小时前
性能优化实战(三):缓存为王-面向缓存的设计
后端·微服务·架构
bug菌3 小时前
Java GUI编程进阶:多线程与并发处理的实战指南
java·后端·java ee