这几种实现线程的方法你一定要知道,月薪20k以上的面试都会问到
实现线程的三种方式总结
最近有看到Java线程的实现相关问题,在此对线程实现方式做一个小小的总结,当做笔记,便于日后查看。
平时常用的线程方式有三种:
(1)、继承Thread类,并重写其run()方法。
(2)、实现Runnable接口,并实现其run()方法。
(3)、实现Callable接口,并实现其call()方法。
一、继承Thread类
Thread 类中创建线程最重要的两个方法为:
public void start();
public void run();
采用 Thread 类创建线程,用户只需要继承 Thread,覆盖 Thread 中的 run 方法,父类 Thread 中的 run 方法没有抛出异常,那么子类也不能抛出异常,最后采用 start 启动线程即可。
【示例代码1】不使用线程
public class ThreadTest01 {
public static void main(String[] args) {
Processor p = new Processor();
p.run();
method1();
}
private static void method1() {
System.out.println("--------method1()----------");
}
}
class Processor {
public void run() {
for (int i=0; i<10; i++) {
System.out.println(i);
}
}
}
【执行结果】
0
1
2
3
4
5
6
7
8
9
--------method1()----------
以上顺序输出相应的结果(属于串行) , 也就是 run 方法完全执行完成后,才执行 method1 方法, 也就是 method1 必须等待前面的方法返回才可以得到执行,这是一种“同步编程模型”。
这样执行存在什么样的弊端呢?
按照顺序执行,这就极大的降低了程序的执行效率。无法同时执行多个代码片段,这也是多线程并发所要达到的目的。
【示例代码2】使用线程
public class ThreadTest02 {
public static void main(String[] args) {
Processor p = new Processor();
/*
如果是手动调用该方法,
则并不能采用 run 来启动一个场景(线程),
run 就是一个普通方法调用。
*/
//p.run();
/*
采用 start 启动线程,不是直接调用 run,
start 不是马上执行线程,而是使线程进入就绪状态
线程的真正执行是由 Java 的线程调度机制完成的。
*/
p.start();
//线程只能启动一次,无法启动多次
//p.start();
method1();
}
private static void method1() {
System.out.println("--------method1()----------");
}
}
class Processor extends Thread {
/*
覆盖 Thread 中的 run 方法,该方法没有异常
该方法是由 java 线程调度机制调用的,因此
我们不应该手动调用该方法
*/
public void run() {
for (int i=0; i<10; i++) {
System.out.println(i);
}
}
}
【执行结果】
--------method1()----------
0
1
2
3
4
5
6
7
8
9
通过输出结果大家会看到,没有按照顺序执行,而在输出数字的同时执行了 method1()方法,如果从效率上看,采用多线程的示例要快些,因为我们可以看作他是同时执行的, mthod1()方法没有等待前面的操作完成才执行, 这叫“异步编程模型”。
那么,为什么会是这样的执行结果呢?
这就涉及到Java线程的调度机制了,该程序包含两个线程一个是主线程也就是main线程,另外一个是用户创建的p线程,当类加载完成后,主线程启动,开始执行main方法栈帧,按照代码自上而下的执行顺序,先创建Processor的实例化对象p,接着是执行p.start();启动p线程,这时method1();方法还没有执行,此时两个线程均已经启动,按照Java线程调度的规则,两个线程开始抢夺执行程序的时间片(即CPU的执行权),注意,这种抢夺是随机的,也就是说,不一定输出结果就是method1方法先执行,for循环语句后执行。可以多执行几次即可看到不一样的执行结果。
二、实现 Runnable 接口
其实 Thread 对象本身就实现了 Runnable 接口,但一般建议直接使用 Runnable 接口来写多线程程序,因为接口会比类带来更多的好处(面向接口编程的原则)。
【示例代码3】
public class ThreadTest03 {
public static void main(String[] args) {
//Processor r1 = new Processor();
/*
使用多态机制父类型引用指向子类型对象,
因为这样可以调用Runnable接口的方法
*/
Runnable r1 = new Processor();
//不能直接调用 run方法,原因见上文
//p.run();
//创建线程对象,并将r1对象作为参数传入(Thread的构造方法)
Thread t1 = new Thread(r1);
//启动线程
t1.start();
method1();
}
private static void method1() {
System.out.println("--------method1()----------");
}
}
//实现 Runnable 接口
class Processor implements Runnable {
//实现 Runnable 中的 run 方法
public void run() {
for (int i=0; i<10; i++) {
System.out.println(i);
}
}
}
【执行结果】
--------method1()----------
0
1
2
3
4
5
6
7
8
9
结果分析见上,原因是一样的,只不过是换了一种方式实现线程而已。
三、实现Callable接口
观察上文两种线程的执行方式,存在什么缺点。显然,以上两种线程的执行un方法时是没有返回值的,而实际上也会存在需要得到线程执行的返回结果的情况,那么怎么办呢?这时就可以考虑使用第三种线程的实现方式。
优点:可以获取到线程的执行结果。
缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
【示例代码4-1】使用匿名内部类
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest04 {
public static void main(String[] args) throws Exception {
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);//当前线程睡眠10秒
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
【执行结果】
call method begin
call method end!
线程执行结果:300
hello world!
【示例代码4-2】不使用匿名内部类
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest01 {
public static void main(String[] args) throws Exception {
//创建Callable接口的实现类的实例化对象
CallableImpl callable = new CallableImpl();
// 第一步:创建一个“未来任务类”对象。
// 参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task = new FutureTask(callable);
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 这里是main方法,这是在主线程中。
// 在主线程中,怎么获取t线程的返回结果?
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想执行必须等待get()方法的结束
// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
// 另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
//实现Callable接口
class CallableImpl implements Callable{
@Override
public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);//当前线程睡眠10秒
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //自动装箱(300结果变成Integer)
}
}
【执行结果】
call method begin
call method end!
线程执行结果:300
hello world!
最后
感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!
这几种实现线程的方法你一定要知道,月薪20k以上的面试都会问到的更多相关文章
- 【Java 线程的深入研究1】Java 提供了三种创建线程的方法
Java 提供了三种创建线程的方法: 通过实现 Runnable 接口: 通过继承 Thread 类本身: 通过 Callable 和 Future 创建线程. 1.通过实现 Runnable 接口来 ...
- C/C++四种退出线程的方法
退出线程可以有四种方法: 1.线程函数的return返回(最好这样): 其中用线程函数的return返回, 而终止线程是最安全的, 在线程函数return返回后, 会清理函数内申请的类对象, 即调用这 ...
- Android 中三种启用线程的方法
在多线程编程这块,我们经常要使用Handler(处理),Thread(线程)和Runnable这三个类,那么他们之间的关系你是否弄清楚了呢? 首先说明Android的CPU分配的最小单元是线程,Han ...
- java三种实现线程的方法比较
1.继承Thread 2.实现Runnable 1和2的比较,1可以创建不同的任务,每个任务互不干扰,对于2,相当于只执行一个任务,多个任务之间互相影响,比如售票系统,每售出一张票,票数都要减1,这个 ...
- Android多线程编程<一>Android中启动子线程的方法
我们知道在Android中,要更新UI只能在UI主线程去更新,而不允许在子线程直接去操作UI,但是很多时候,很多耗时的工作都交给子线程去实现,当子线程执行完这些耗时的工作后,我们希望去修改 ...
- Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量
Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程: 1.线程是一堆指令,是操作系统调度 ...
- Qt新建线程的方法(四种办法,很详细,有截图)
看了不少Qt线程的东西,下面总结一下Qt新建一个线程的方法. 一.继承QThread 继承QThread,这应该是最常用的方法了.我们可以通过重写虚函数void QThread::run ()实现我们 ...
- 四种Java线程池用法解析
本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...
- 远程线程注入方法CreateRemoteThread
最近在整理学习Windows注入方面的知识,这个远程注入前面早写过,现在看看人家博客的理解整理,整理, 需要源码的可以到我的github上下载. 链接是 https://github.com/Ars ...
随机推荐
- 想买保时捷的运维李先生学Java性能之 运行时数据区域
前言 不知道自己不知道,不知道自己知道,知道自己不知道,知道自己知道,目前处于知道自己不知道这个阶段,很痛苦啊,干了4年了运维,是一个坎.越来越发觉想要走得远,还是得扎根底. 一.运行时数据区域 ...
- 安卓WebSocket使用
引入jar包: implementation "org.java-websocket:Java-WebSocket:1.4.0"implementation "org.s ...
- 如何实现一个FormData
一.前言 最近项目中遇到一个问题,我们需要在cocos项目里去上传音频文件,而cocos原生环境和平时我们开发所在的浏览器环境和Node环境有很多差异,而cocos环境只提供了基础类,没有提供Form ...
- 必须掌握的分布式文件存储系统—HDFS
HDFS(Hadoop Distributed File System)分布式文件存储系统,主要为各类分布式计算框架如Spark.MapReduce等提供海量数据存储服务,同时HBase.Hive底层 ...
- (Pytorch)涉及的常见操作
涉及一些pytorch的API内容在此进行整理 损失函数:Binary-Cross-Entropy loss criterion = nn.BCECriterion() 创建一个标准来度量目标和输出之 ...
- Linux防火墙篇
关闭firewall:systemctl stop firewalld.service #停止firewallsystemctl disable firewalld.service #禁止f ...
- Java动态代理——框架中的应用场景和基本原理
前言 之前已经用了5篇文章完整解释了java动态代理的原理,本文将会为这个系列补上最后一块拼图,展示java动态代理的使用方式和应用场景 主要分为以下4个部分 1.为什么要使用java动态代理 2.如 ...
- Hadoop高可用
一.原因 - NameNode是HDFS的黑心配置HDFS有事hadoop的核心组件 NameNode 在Hadoop及群众至关重要 - NameNode的宕机导致集群的不可用 二.解决方案 其中 N ...
- Socket创建简单服务器和客户端程序
使用Socket编程创建简单服务器和客户端 要知道的 Socket-AddressFamily, SocketType, ProtocolType https://blog.csdn.net/weix ...
- 【译】关于Rust模块的清晰解释
原文链接: http://www.sheshbabu.com/posts/rust-module-system/ 原文标题: Clear explanation of Rust's module sy ...