多线程开发实战:Java实现多线程四种方式及相关方法原理_java多线程有几种实现方法-程序员宅基地

技术标签: jvm  java  servlet  Java全栈编程实战  开发语言  

78e52c393e19483398d4ac6a93453582.png

 个人简介

‍个人主页:陈橘又青

‍♂️博客记录心情,代码编写人生。

如果文章对你有用,麻烦关注点赞收藏走一波,感谢支持!

欢迎订阅我的专栏:

【Java进阶之路】带你入门Java,从0到1。

【Java全栈编程】带你项目实战,掌握核心。

前言

Java多线程实现方式主要有四种:

① 继承Thread类、实现Runnable接口

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

③ 使用ExecutorService、Callable

④ Future实现有返回结果的多线程

其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。


目录

 一、四种方式实现多线程

1、继承Thread类创建线程

2、实现Runnable接口创建线程

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

4、使用ExecutorService、Callable、Future实现有返回结果的线程

二、多线程相关知识

1.Runnable 和 Callable 的区别?

 2. 如何启动一个新线程、调用 start 和 run 方法的区别?

3.线程相关的基本方法?

5.wait()和 sleep()的区别?

6.多线程原理


 一、四种方式实现多线程

1、继承Thread类创建线程

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

public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  

MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start();

2、实现Runnable接口创建线程

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口,如下:

public class MyThread extends OtherClass implements Runnable {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}

为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start();

事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:

public void run() {  
  if (target != null) {  
   target.run();  
  }  
}

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

Callable接口(也只有一个方法)定义如下:

public interface Callable<V>   { 
  V call() throws Exception;   } 

public class SomeCallable<V> extends OtherClass implements Callable<V> {

    @Override
    public V call() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

}
Callable<V> oneCallable = new SomeCallable<V>();   
//由Callable<Integer>创建一个FutureTask<Integer>对象:   
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);   
//注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。 
  //由FutureTask<Integer>创建一个Thread对象:   
Thread oneThread = new Thread(oneTask);   
oneThread.start();   //至此,一个线程就创建完成了。

4、使用ExecutorService、Callable、Future实现有返回结果的线程

ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。

可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。

执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。

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

再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。

下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:

import java.util.concurrent.*;  
import java.util.Date;  
import java.util.List;  
import java.util.ArrayList;  

/** 
* 有返回值的线程 
*/  
@SuppressWarnings("unchecked")  
public class Test {  
public static void main(String[] args) throws ExecutionException,  
    InterruptedException {  
   System.out.println("----程序开始运行----");  
   Date date1 = new Date();  

   int taskSize = 5;  
   // 创建一个线程池  
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
   // 创建多个有返回值的任务  
   List<Future> list = new ArrayList<Future>();  
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable(i + " ");  
    // 执行任务并获取Future对象  
    Future f = pool.submit(c);  
    // System.out.println(">>>" + f.get().toString());  
    list.add(f);  
   }  
   // 关闭线程池  
   pool.shutdown();  

   // 获取所有并发任务的运行结果  
   for (Future f : list) {  
    // 从Future对象上获取任务的返回值,并输出到控制台  
    System.out.println(">>>" + f.get().toString());  
   }  

   Date date2 = new Date();  
   System.out.println("----程序结束运行----,程序运行时间【"  
     + (date2.getTime() - date1.getTime()) + "毫秒】");  
}  
}  

class MyCallable implements Callable<Object> {  
private String taskNum;  

MyCallable(String taskNum) {  
   this.taskNum = taskNum;  
}  

public Object call() throws Exception {  
   System.out.println(">>>" + taskNum + "任务启动");  
   Date dateTmp1 = new Date();  
   Thread.sleep(1000);  
   Date dateTmp2 = new Date();  
   long time = dateTmp2.getTime() - dateTmp1.getTime();  
   System.out.println(">>>" + taskNum + "任务终止");  
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
}  
}

二、多线程相关知识

1.Runnable 和 Callable 的区别?

 · 主要区别 Runnable 接口 run 方法无返回值;

 · Callable 接口 call 方法有返回值,支持泛型 Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;

 · Callable 接口 call 方 法允许抛出异常,可以获取异常信息

 2. 如何启动一个新线程、调用 start 和 run 方法的区别?

 · 线程对象调用 run 方法不开启线程。仅是对象调用方法。

 · 线程对象调用 start 开启线程,并让 jvm 调用 run 方法在开启的线程中执行调用 start 方法可以启动线程,并且使得线程进入就绪状态,而 run 方法只是 thread 的一 个普通方法,还是在主线程中执行。

3.线程相关的基本方法?

​ 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等

 · 线程等待(wait) 调用该方法的线程进入 waiting状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象 的锁。因此,wait 方 法一般用在同步方法或同步代码块中。

 · 线程睡眠(sleep) sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占 有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法 会导致当前线程进入 WATING 状态.

 · 线程让步(yield) yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。

 · 线程中断(interrupt) 中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的 一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)

 · Join 等待其他线程终止 join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方 法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变 为就绪状态,等待 cpu 的宠幸.

 · 线程唤醒(notify) Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如 果所有线程都在此对象上等待,则会选择唤醒其中一个线 程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视 器上等待,直到当前的线程放弃此对象上的锁 定,才能继续执行被唤醒的线程, 被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类 似的方法还有 notifyAll() ,唤醒再 次监视器上等待的所有线程。

5.wait()和 sleep()的区别?

 · ① 来自不同的类 wait():来自 Object 类; sleep():来自 Thread 类;

 · ② 关于锁的释放: wait():在等待的过程中会释放锁; sleep():在等待的过程中不会释放锁

 · ③ 使用的范围: wait():必须在同步代码块中使用; sleep():可以在任何地方使用;

 · ④ 是否需要捕获异常 wait():不需要捕获异常; sleep():需要捕获异常;

6.多线程原理

    多线程原理:多线程是通过并发的方式进行。对于一个CPU它在某个时间点上,只能执行一个程序,即同一时间只能运行一个进程,CPU会不断地在这些进程之间切换,每个线程执行一个时间。因为CPU的执行速度相对我们的感觉实在太快了,虽然CPU在多个进程之间轮换执行,但我们自己感到好像多个进程在同时执行。

    CPU会在多个进程之间做着切换,如果我们开启的程序过多,CPU切换到每一个进程的时间也会变长,我们也会感觉机器运行变慢。所以合理的使用多线程可以提高效率,但是大量使用,并不能给我们带来效率上的提高。

    多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
 

a7a4b799da0945089dd3eedffc561db9.png


 以上就是本篇博客的全部内容,如果对你有用麻烦关注点赞收藏三连走一波!
如有问题欢迎留言,如有错误烦请指正,期待你的评论!

6df8cced6a694d93b70fb00140789b08.png

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_63947499/article/details/125300866

智能推荐

HDLbits错题_error (10734): verilog hdl error at mca_out_ctrl.v-程序员宅基地

文章浏览阅读401次。1.BCDadd100参考HDLBits Day5 Bcdadd100变量下标中有变量的情况注意大小端表示步骤 注意整个工程大小端必须一致1.定义的变量是大端还是小端模式2.看升序(+:)还是降序(-:)3.看位宽并进行转换定义reg [31:0] big_vect;reg [0:31] little_vect;解释big_vect [0 +: 8] 从0 开始,升序,位宽为8 ======》》》》》big_vect [7 :0]little_vect _error (10734): verilog hdl error at mca_out_ctrl.v(16): p is not a constant

python编程,从入门到实践:第七章_python大学生刘海在唐人街开了一间餐馆用编程帮助刘海点菜和结账-程序员宅基地

文章浏览阅读355次。python编程,从入门到实践:第七章7-1 汽车租赁 汽车租赁 :编写一个程序,询问用户要租赁什么样的汽车,并打印一条消息,如“Let me see if I can find you a Subaru”。7-2 餐馆订位 餐馆订位 :编写一个程序,询问用户有多少人用餐。如果超过8人,就打印一条消息,指出没有空桌;否则指出有空桌。 7-3 10的整数倍 的整数倍 :让用户输入一个数字,并指出..._python大学生刘海在唐人街开了一间餐馆用编程帮助刘海点菜和结账

解决SpringCloud客户端启动报错:“Field XXX required a bean of type XXX that could not be found”_升级springcloud required a bean of type interface th-程序员宅基地

文章浏览阅读920次。消费方调用提供方的方法@EnableFeignClients //添加fegin注解,调用其他服务方法_升级springcloud required a bean of type interface that could not be found.

JAVA-数组例题(随机排名)_某公司开发部5名开发人员,要进行项目进展汇报演讲,现在采取随机排名后进行汇报。-程序员宅基地

文章浏览阅读997次,点赞4次,收藏5次。题目:猜数字游戏要求:(1)游戏后台随机生成1-20之间的5个数(无所谓是否重复),然后让大家来猜数字, 为猜中_某公司开发部5名开发人员,要进行项目进展汇报演讲,现在采取随机排名后进行汇报。

博科300 java配置,Brocade_300光纤交换机配置重点详解.ppt-程序员宅基地

文章浏览阅读450次。Brocade SAN·Ed Pro SilkWorm Product Training 第一章 共42页 * 为何要为存储提供专用网络哪? 答: 好的局域网并不意味着好的存储区域网 LAN使用不同的协议不同的工具 LAN在客户端缺少安全性, 而服务器端则潜伏着易收攻击的缺点 LAN很少能够为存储网络化提供相应的冗余数据传..._博科300 jre

物理单位与格子单位转换_格子boltzmann单位换算-程序员宅基地

文章浏览阅读2.4k次,点赞2次,收藏25次。在使用格子-玻尔兹曼方法模拟时,使用的是格子单位,而非实际的物理单位。物理单位与各自单位的转换非常重要!非常重要!非常重要!一方面,在转换过程中容易出错;另一方面,不选择合适的参数,容易导致不稳定(发散)。在这里主要介绍一下几个简单的单位之间的转换。转换原则:主要依据就是量纲关系以及各物理量之间的关系。其中最重要的是:特征数保持不变!以雷诺数为例,理论上,在使得物理雷诺数与格子雷诺数相等的情况下,格子速度、格子运动粘度、格子数量(这里的格子数量是看你取的什么雷诺数,如果是颗粒雷诺数,那就是格_格子boltzmann单位换算

随便推点

python ssh实时交互_paramiko实现ssh的交互式连接-程序员宅基地

文章浏览阅读641次。利用python3+实现,命名为interactive.pyimport socket import sys # windows does not have termios... try: import termios import tty has_termios = True except ImportError: has_termios = False def ..._python连接ssh怎么遇到交互命令

python怎么定义全局字典_一种Python全局配置规范以及其魔改-程序员宅基地

文章浏览阅读2.9k次。0x01 模块 or global很多初学者有个误区,就是在Python中需要配置一个全局的参数时,首先想到的是global关键字,而实际上global不是干这个事的,global的功能是在将局部作用域的变量声明为全局的,这样可以在局部修改全局的变量。但这种用法其实非常不好,按照函数式的规范而言,纯函数的输入应该只由输入参数确定,不应该在执行过程中引用外部变量。并且,global也不是用来进行全局..._python 全局字典

[C#] 我的log4net使用手册(完善中)_c#中ilog类的说明-程序员宅基地

文章浏览阅读2w次。1. log4net简介log4net是.Net下一个非常优秀的开源日志记录组件。log4net记录日志的功能非常强大。它可以将日志分不同的等级,以不同的格式,输出到不同的媒介。Java平台下,它还有一个姐妹组件——log4j。log4net的下载地址:http://logging.apache.org/log4net/download.html 2. log4net的组成lo_c#中ilog类的说明

毕设项目分享 单片机贪吃蛇游戏(AT89C51)-程序员宅基地

文章浏览阅读287次,点赞4次,收藏4次。此电路由AT89C51最小系统、74HC595位移缓存器、8*8点阵LED屏和按键组成。74HC595是一个8位串行输入、并行输出的位移缓存器:并行输出为三态输出。在SCK的上升沿,串行数据由SDL输入到内部的8位位移缓存器,并由Q7’输出,而并行输出则是在LCK的上升沿将在8位位移缓存器的数据存入到8位并行输出缓存器。当串行数据输入端OE的控制信号为低使能时,并行输出端的输出值等于并行输出缓存器所存储的值。

onnxruntime推理YOLOv5-程序员宅基地

文章浏览阅读223次。YOLOv5的onnxruntime推理_onnxruntime推理yolov5

Linux主机通过虚机中的EasyConnect连接内网_easyconnect 虚拟机-程序员宅基地

文章浏览阅读3.5k次。根据 https://smartkeyerror.com/Linux-Use-EasyConnect 文章所述,尝试安装了一下,确实可行。虽然每次使用有点麻烦,但依然是一个选项:虚机启动后必须登录 EasyConnect,这客户端登录时还有个验证图片,也没个证书,所以没有测试自动登录。不过第二条就已经打消了自动建立连接的想法了。被共享的连接必须重建,否则192.168.137.1网卡无法转发数据包。我猜使用管理员控制台用命令可以解决,有空再折腾吧。Linux主机使用了 remmina,没找到哪里可_easyconnect 虚拟机

推荐文章

热门文章

相关标签