在Java中,多线程主要的实现方式有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,而后两种是带返回值的。除此之外,通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务。

1、继承Thread类创建线程

Thread类本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程比较简单,通过继承Thread类并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

CreateThreadDemo1.java

public class CreateThreadDemo1 extends Thread {

    public CreateThreadDemo1(String name) {
// 设置当前线程的名字
this.setName(name);
} @Override
public void run() {
System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
} public static void main(String[] args) throws Exception {
// 注意这里,要调用start方法才能启动线程,不能调用run方法
new CreateThreadDemo1("MyThread1").start();
new CreateThreadDemo1("MyThread2").start(); } }

输出结果:

当前运行的线程名为: MyThread1
当前运行的线程名为: MyThread2

2、实现Runnable接口创建线程
由于Java是单继承机制,如果自己的类已经继承自另一个类,则无法再直接继承Thread类,此时,可以通过实现Runnable接口来实现多线程。

实现Runnable接口并实现其中的run方法,然后通过构造Thread实例,传入Runnable实现类,然后调用Thread的start方法即可开启一个新线程。

CreateThreadDemo2.java

public class CreateThreadDemo2 implements Runnable {

    @Override
public void run() {
System.out.println("当前运行的线程名为: " + Thread.currentThread().getName());
} public static void main(String[] args) throws Exception {
CreateThreadDemo2 runnable = new CreateThreadDemo2();
new Thread(runnable, "MyThread1").start();
new Thread(runnable, "MyThread2").start(); } }

输出结果:

当前运行的线程名为: MyThread1
当前运行的线程名为: MyThread2

3、实现Callable接口通过FutureTask包装器来创建Thread线程

首先需要一个实现Callable接口的实例,然后实现该接口的唯一方法call逻辑,接着把Callable实例包装成FutureTask传递给Thread实例启动新线程。FutureTask本质上也实现了Runnable接口,所以同样可以用来构造Thread实例。

CreateThreadDemo3.java

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; public class CreateThreadDemo3 { public static void main(String[] args) throws Exception {
// 创建线程任务,lambada方式实现接口并实现call方法
Callable<Integer> callable = () -> {
System.out.println("线程任务开始执行了...");
Thread.sleep(2000);
return 1;
}; // 将任务封装为FutureTask
FutureTask<Integer> task = new FutureTask<>(callable); // 开启线程,执行线程任务
new Thread(task).start(); // ====================
// 这里是在线程启动之后,线程结果返回之前
System.out.println("线程启动之后,线程结果返回之前...");
// ==================== // 为所欲为完毕之后,拿到线程的执行结果
Integer result = task.get();
System.out.println("主线程中拿到异步任务执行的结果为:" + result); } }

输出结果:

线程启动之后,线程结果返回之前...
线程任务开始执行了...
主线程中拿到异步任务执行的结果为:1

4、使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

ExecutorService、Callable、Future三个接口都是属于Executor框架。可返回值的任务必须实现Callable接口。通过ExecutorService执行Callable任务后,可以获取到一个Future的对象,在该对象上调用get()就可以获取到Callable任务返回的结果了。

注意:Future的get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

CreateThreadDemo4.java

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class CreateThreadDemo4 { @SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("---- 主程序开始运行 ----");
Date startTime = new Date(); int taskSize = 5;
// 创建一个线程池,Executors提供了创建各种类型线程池的方法,具体详情请自行查阅
ExecutorService executorService = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务
List<Future> futureList = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable callable = new MyCallable(i);
// 执行任务并获取Future对象
Future future = executorService.submit(callable);
futureList.add(future);
} // 关闭线程池
executorService.shutdown(); // 获取所有并发任务的运行结果
for (Future future : futureList) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>> " + future.get().toString());
} Date endTime = new Date();
System.out.println("---- 主程序结束运行 ----,程序运行耗时【" + (endTime.getTime() - startTime.getTime()) + "毫秒】");
}
} class MyCallable implements Callable<Object> {
private int taskNum; MyCallable(int taskNum) {
this.taskNum = taskNum;
} public Object call() throws Exception {
System.out.println(">>> " + taskNum + " 线程任务启动");
Date startTime = new Date();
Thread.sleep(1000);
Date endTime = new Date();
long time = endTime.getTime() - startTime.getTime();
System.out.println(">>> " + taskNum + " 线程任务终止");
return taskNum + "线程任务返回运行结果, 当前任务耗时【" + time + "毫秒】";
}
}

输出结果:

---- 主程序开始运行 ----
>>> 0 线程任务启动
>>> 1 线程任务启动
>>> 2 线程任务启动
>>> 3 线程任务启动
>>> 4 线程任务启动
>>> 0 线程任务终止
>>> 1 线程任务终止
>>> 0线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 1线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4 线程任务终止
>>> 3 线程任务终止
>>> 2 线程任务终止
>>> 2线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 3线程任务返回运行结果, 当前任务耗时【1001毫秒】
>>> 4线程任务返回运行结果, 当前任务耗时【1001毫秒】
---- 主程序结束运行 ----,程序运行耗时【1009毫秒】

5、其他创建线程的方式

当然,除了以上四种主要的线程创建方式之外,也还有很多其他的方式可以启动多线程任务。比如通过Timer启动定时任务,或者通过像Spring Task和quartz这样的第三方任务调度框架也可以开启多线程任务,关于第三方任务调度框架的例子还请查询相关资料。

源码下载

码云:https://gitee.com/liuge1988/java-demo.git


作者:朝雨忆轻尘
出处:https://www.cnblogs.com/xifengxiaoma/
版权所有,欢迎转载,转载请注明原文作者及出处。

Java并发编程:Java实现多线程的几种方式的更多相关文章

  1. Java并发编程-Java内存模型

    JVM内存结构与Java内存模型经常会混淆在一起,本文将对Java内存模型进行详细说明,并解释Java内存模型在线程通信方面起到的作用. 我们常说的JVM内存模式指的是JVM的内存分区:而Java内存 ...

  2. Java并发编程:Java Thread 的 sleep() 和 wait() 的区别

      1. start 和 run 方法解释: 1) start: 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码.通过调用Thread类 ...

  3. Python并发编程之创建多线程的几种方法(二)

    大家好,并发编程 进入第二篇. 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础.学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章. 本文目录 学会使用函数创建多 ...

  4. python 之 并发编程(开启子进程的两种方式,进程对象的属性)

    第九章并发编程 同一个程序执行多次是多个进程 import time import os ​ print('爹是:',os.getppid()) #父进程PID,(pycharm) print('me ...

  5. JAVA并发编程学习笔记------多线程调优

    1. 多线程场景下尽量使用并发容器代替同步容器 (如ConcurrentHashMap代替同步且基于散列的Map, 遍历操作为主要操作的情况下用CopyOnWriteArrayList代替同步的Lis ...

  6. Java并发编程(03):多线程并发访问,同步控制

    本文源码:GitHub·点这里 || GitEE·点这里 一.并发问题 多线程学习的时候,要面对的第一个复杂问题就是,并发模式下变量的访问,如果不理清楚内在流程和原因,经常会出现这样一个问题:线程处理 ...

  7. 【Java并发编程】:多线程环境中安全使用集合API

    在集合API中,最初设计的Vector和Hashtable是多线程安全的.例如:对于Vector来说,用来添加和删除元素的方法是同步的.如果只有一个线程与Vector的实例交互,那么,要求获取和释放对 ...

  8. Java并发编程:Java Thread 的 run() 与 start() 的区别

    1. sleep 和 wait 方法解释 sleep()方法是Thread类里面的,主要的意义就是让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后 ...

  9. JAVA并发编程学习笔记------线程的三种创建方式

    创建线程一般有如下几个方式: 1. 通过继承Thread类来创建一个线程: /** * 步骤1:定义一个继承Thread类的子类 * 步骤2:构造子类的一个对象 * 步骤3:启动线程: * */ pu ...

  10. Java并发编程:Java Thread方法join的简单总结

    虽然关于讨论线程join方法的博客已经很多了,不过个人感觉挺多都讨论得不够全面,所以我觉得有必要对其进行一个全面的总结. 一.作用 Thread类中的join方法的主要作用就是同步,它可以使得线程之间 ...

随机推荐

  1. Android lifecyle 源码解剖 - gdutxiaoxu的博客(微信公众号 stormjun94)

    版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/gdutxiaoxu/article/det ...

  2. Visual Studio安装工具和安装组件下载速度慢的问题

    下载安装Visual Studio时在下面这个界面下载时速度只有几十KB每秒 解决办法:修改Host文件,在Host文件中添加下面代码,然后保存即可 110.53.72.104 download.vi ...

  3. 监控利器-Prometheus安装与部署+实现邮箱报警

    Prometheus(普罗米修斯)监控 环境准备: 三台docker主机(centos7):docker01:172.16.1.30部署服务:Prometheus server,Grafana,Nod ...

  4. python与数据库交互的模块pymysql

    一.Mysql 1.前提 pip install pymysql import pymysql 2.详情 Connection对象 =====>用于连接数据库 用于建立与数据库的连接 创建对象: ...

  5. R期望

    斐波那契数列--九九乘法表 # 1. 打印斐波那契数列 kl<-c(1,1) for (i in 1:8){ kl[i+2]<-kl[i]+kl[i+1] } kl # 10. 打印九九乘 ...

  6. 你必须知道的Dockerfile

    本篇已加入<.NET Core on K8S学习实践系列文章索引>,可以点击查看更多容器化技术相关系列文章. 一.关于Dockerfile 在Docker中创建镜像最常用的方式,就是使用D ...

  7. netty源码解析(4.0)-27 ByteBuf内存池:PoolArena-PoolThreadCache

    前面两章分析的PoolChunk和PoolSubpage,从功能上来说已经可以直接拿来用了.但直接使用这个两个类管理内存在高频分配/释放内存场景下会有性能问题,PoolChunk分配内存时算法复杂度最 ...

  8. 保护模式中的PDE与PTE

    Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html 保护模式中的PDE与PTE 1. PDE与PTE的认知 我们在上一 ...

  9. C# - VS2019 DataGridView导出到Excel的三种方法

    //原文出处:http://www.yongfa365.com/Item/DataGridViewToExcel.html 1 #region DataGridView数据显示到Excel /// & ...

  10. scrapy在pycharm配置启动(无需命令行启动)

    一.新建文件 run.py这个名字随意哈 方法一. from scrapy.cmdline import execute execute(['scrapy','crawl','爬虫程序名字','-a' ...