作者:唐彤
简介
创建线程,是多线程编程中最基本的操作,彤哥总结了一下,大概有8种创建线程的方式,你知道吗?
1.继承Thread类并重写run()方法
public class CreatingThread01 extends Thread { @Override public void run() { System.out.println(getName() + " is running"); } public static void main(String[] args) { new CreatingThread01().start(); new CreatingThread01().start(); new CreatingThread01().start(); new CreatingThread01().start(); } }
继承Thread类并重写run()方法,这种方式的弊端是一个类只能继承一个父类,如果这个类本身已经继承了其它类,就不能使用这种方式了。
2.实现Runnable接口
public class CreatingThread02 implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } public static void main(String[] args) { new Thread(new CreatingThread02()).start(); new Thread(new CreatingThread02()).start(); new Thread(new CreatingThread02()).start(); new Thread(new CreatingThread02()).start(); } }
实现Runnable接口,这种方式的好处是一个类可以实现多个接口,不影响其继承体系。
3.匿名内部类
public class CreatingThread03 { public static void main(String[] args) { // Thread匿名类,重写Thread的run()方法 new Thread() { @Override public void run() { System.out.println(getName() + " is running"); } }.start(); // Runnable匿名类,实现其run()方法 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } }).start(); // 同上,使用lambda表达式函数式编程 new Thread(()->{ System.out.println(Thread.currentThread().getName() + " is running"); }).start(); } }
使用匿名类的方式,一是重写Thread的run()方法,二是传入Runnable的匿名类,三是使用lambda方式,现在一般使用第三种(java8+),简单快捷。
4.实现Callabe接口
public class CreatingThread04 implements Callable<Long> { @Override public Long call() throws Exception { Thread.sleep(2000); System.out.println(Thread.currentThread().getId() + " is running"); return Thread.currentThread().getId(); } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Long> task = new FutureTask<>(new CreatingThread04()); new Thread(task).start(); System.out.println("等待完成任务"); Long result = task.get(); System.out.println("任务结果:" + result); } }
实现Callabe接口,可以获取线程执行的结果,FutureTask实际上实现了Runnable接口。
5.定时器(java.util.Timer)
public class CreatingThread05 { public static void main(String[] args) { Timer timer = new Timer(); // 每隔1秒执行一次 timer.schedule(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } }, 0 , 1000); } }
使用定时器java.util.Timer可以快速地实现定时任务,TimerTask实际上实现了Runnable接口。
6.线程池
public class CreatingThread06 { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(5); for (int i = 0; i < 100; i++) { threadPool.execute(()-> System.out.println(Thread.currentThread().getName() + " is running")); } } }
使用线程池的方式,可以复用线程,节约系统资源。
7.并行计算(Java8+)
public class CreatingThread07 { public static void main(String[] args) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); // 串行,打印结果为12345 list.stream().forEach(System.out::print); System.out.println(); // 并行,打印结果随机,比如35214 list.parallelStream().forEach(System.out::print); } }
使用并行计算的方式,可以提高程序运行的效率,多线程并行执行。
8.Spring异步方法
首先,springboot启动类加上 @EnableAsync注解(@EnableAsync是spring支持的,这里方便举例使用springboot)。
@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
其次,方法加上 @Async注解。
@Service public class CreatingThread08Service { @Async public void call() { System.out.println(Thread.currentThread().getName() + " is running"); } }
然后,测试用例直接跟使用一般的Service方法一模一样。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class CreatingThread08Test { @Autowired private CreatingThread08Service creatingThread08Service; @Test public void test() { creatingThread08Service.call(); creatingThread08Service.call(); creatingThread08Service.call(); creatingThread08Service.call(); } }
运行结果如下:
task-3 is running task-2 is running task-1 is running task-4 is running
可以看到每次执行方法时使用的线程都不一样。
使用Spring异步方法的方式,可以说是相当地方便,适用于前后逻辑不相关联的适合用异步调用的一些方法,比如发送短信的功能。
总结
(1)继承Thread类并重写run()方法;
(2)实现Runnable接口;
(3)匿名内部类;
(4)实现Callabe接口;
(5)定时器(java.util.Timer);
(6)线程池;
(7)并行计算(Java8+);
(8)Spring异步方法;
福利
上面介绍了那么多创建线程的方式,其实本质上就两种,一种是继承Thread类并重写其run()方法,一种是实现Runnable接口的run()方法,那么它们之间到底有什么联系呢?
请看下面的例子,同时继承Thread并实现Runnable接口,应该输出什么呢?
public class CreatingThread09 { public static void main(String[] args) { new Thread(()-> { System.out.println("Runnable: " + Thread.currentThread().getName()); }) { @Override public void run() { System.out.println("Thread: " + getName()); } }.start(); } }
说到这里,我们有必要看一下Thread类的源码:
public class Thread implements Runnable { // Thread维护了一个Runnable的实例 private Runnable target; public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // ... // 构造方法传进来的Runnable会赋值给target this.target = target; // ... } @Override public void run() { // Thread默认的run()方法,如果target不为空,会执行target的run()方法 if (target != null) { target.run(); } } }
看到这里是不是豁然开朗呢?既然上面的例子同时继承Thread并实现了Runnable接口,根据源码,实际上相当于重写了Thread的run()方法,在Thread的run()方法时实际上跟target都没有关系了。
所以,上面的例子输出结果为 Thread:Thread-0,只输出重写Thread的run()方法中的内容。
最后
欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!

8种创建Java线程的方式,你知道几个?的更多相关文章

  1. 5.创建执行线程的方式之三 :实现Callable 接口

    Callable 接口 一.Java 5.0 在 java.util.concurrent 提供了 一个新的创建执行线程的方式(之前有继承Thread 和 实现Runnable):Callable 接 ...

  2. 一步一步创建JAVA线程

    (一)创建线程 要想明白线程机制,我们先从一些基本内容的概念下手. 线程和进程是两个完全不同的概念,进程是运行在自己的地址空间内的自包容的程序,而线程是在进程中的一个单一的顺序控制流,因此,单个进程可 ...

  3. 按照阿里巴巴规范创建Java线程池

    前言 Executors Executors 是一个Java中的工具类.提供工厂方法来创建不同类型的线程池. 常用方法: 1.newSingleThreadExecutor   介绍:创建一个单线程的 ...

  4. Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

    1.Java使用Thread类代表线程.     所有的线程对象必须是Thread类或其子类的实例. 当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName() ...

  5. 阶段3 2.Spring_03.Spring的 IOC 和 DI_6 spring中bean的细节之三种创建Bean对象的方式

    目前这里能调用是因为,在service的实现类里面,new了一个dao的对象 正常情况下 这里不应该是new一个对象,应该等于null或为空 设置为空侯再运行就会报错 出错的原因是这里为null 需要 ...

  6. 创建Java线程池

    线程池的作用: 线程池作用就是限制系统中执行线程的数量. 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数量,其他线 ...

  7. spring:bean的细节之三种创建Bean对象的方式

    <!--创建Bean的三种方式--><!--第一种方式,使用默认构造函数创建 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有属性和标签时. 采用的就 ...

  8. SPC2014 :“FOSL”不是替代InfoPath,只是另外一种创建表单的方式

    今天在SPC2014微软宣布他们技术路线图.其实,没有足够证据替代InfoPath,只是另外的一种尝试 - FOSL(对SharePoint列表表单). FOSL使用相同的引擎,用于创建表单的访问服务 ...

  9. Java线程之创建线程

    翻译自:https://www.journaldev.com/1016/java-thread-example 进程 进程是一个自包含的执行环境,它可以被看成一个程序或应用程序.然而一个应用程序本身包 ...

随机推荐

  1. ACE框架 基于共享内存的分配器

    ACE框架提供了一个内存分配器模板,并且提供了(仅且)一个模板实例,基于共存内存的内存分配器.这个共存内存分配器模板实例在ACE框架应用于,基于内存映射的进程通讯,以及进程间同步等. ACE内存分配器 ...

  2. SpringBoot 源码解析 (八)----- Spring Boot 精髓:事务源码解析

    本篇来讲一下SpringBoot是怎么自动开启事务的,我们先来回顾一下以前SSM中是如何使用事务的 SSM使用事务 导入JDBC依赖包 众所周知,凡是需要跟数据库打交道的,基本上都要添加jdbc的依赖 ...

  3. 关于element-ui级联菜单(城市三级联动菜单)和回显问题

    https://segmentfault.com/a/1190000020458087 这是我写的,可以去看看,希望对你们有帮助!!!

  4. linux 进程简介

    进程相关知识简介 进程定义: 一个运行中的程序即一个process task struct: 内核存储进程信息的固定格式称为task struct,task struct记录了例如该进程内存下一跳位置 ...

  5. 计蒜客 The Preliminary Contest for ICPC Asia Nanjing 2019

    F    Greedy Sequence You're given a permutation aa of length nn (1 \le n \le 10^51≤n≤105). For each  ...

  6. 前后端vue和django配置

    django pip源更换 虚拟环境配置 目录和日志配置 数据库配置 用户模块自定义 异常,响应配置 xadmin vue vue基础配置 vue高级配置 vue this问题和样式配置

  7. 题解 CF1206B 【Make Product Equal One】

    感谢 @一个低调的人 (UID=48417) 题目: CodeForces链接 Luogu链接 思路: 这是一个一眼题 我们不妨把所有的数都看做是\(1\)(取相应的花费,如:\(6\) 的花费就是\ ...

  8. lvm_lv_extend

    根分区lv扩容 xfs格式 neokylinV7.0 [root@localhost ~]# fdisk /dev/vda 欢迎使用 fdisk (util-linux 2.23.2). 更改将停留在 ...

  9. js消除小游戏(极简版)

    js小游戏极简版 (1) 基础布局 <div class = "box"> <p></p> <div class="div&qu ...

  10. PHP经典面试题目汇总

    1.双引号和单引号的区别 双引号解释变量,单引号不解释变量 双引号里插入单引号,其中单引号里如果有变量的话,变量解释 双引号的变量名后面必须要有一个非数字.字母.下划线的特殊字符,或者用{}讲变量括起 ...