Java并发编程:Java创建线程的三种方式
引言
在日常开发工作中,多线程开发可以说是必备技能,好的程序员是一定要对线程这块有深入了解的,我是Java程序员,并且Java语言本身对于线程开发的支持是非常成熟的,所以今天我们就来入个门,学一下Java怎么创建线程。
创建线程的三种方式
Java创建线程主要有三种方式:
1、继承Thread类
2、实现Runnable接口
3、使用Callable和Future创建线程
下面分别讨论这三种方法的实现方式,以及它们之间的对比。
一、继承Thread类
步骤:
1、创建一个线程子类继承Thread类
2、重写run() 方法,把需要线程执行的程序放入run方法,线程启动后方法里的程序就会运行
2、创建该类的实例,并调用对象的start()方法启动线程
示例代码如下:
public class ThreadDemo extends Thread{
@Override
public void run() {
super.run();
System.out.println("需要运行的程序。。。。。。。。");
}
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
}
}
当运行main方法后,程序就会执行run()方法里面的内容,执行完之后,线程也就随之消亡,为什么一定要重写run()方法呢?
点击方法的源码后,发现Thread的run()方法其实什么都没有做
public void run() {
if (target != null) {
target.run();
}
}
public abstract void run();
如果run()里没有需要运行的程序,那么线程启动后就直接消亡了。想让线程做点什么就必须重写run()方法。同时,还需要注意的是,线程启动需要调用start()方法,但直接调用run() 方法也能编译通过,也能正常运行:
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.run();
}
只是这样是普通的方法调用,并没有新起一个线程,也就失去了线程本身的意义。
二、实现Runnable接口
1、定义一个线程类实现Runnable接口,并重写该接口的run()方法,方法中依然是包含指定执行的程序。
2、创建一个Runnable实现类实例,将其作为target参数传入,并创建Thread类实例。
3、调用Thread类实例的start()方法启动线程。
public class RunnableDemo implements Runnable{
@Override
public void run() {
System.out.println("我是Runnable接口......");
}
public static void main(String[] args) {
RunnableDemo demo = new RunnableDemo();
Thread thread = new Thread(demo);
thread.start();
}
}
这是基于接口的方式,比起继承Thread的方式要灵活很多,但需要多创建一个线程对象,打开源码可以发现,当把Runnable实现类的实例作为参数target传入后,赋值给当前线程类的target,而run()里执行的程序就是赋值进去的target的run()方法。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...........这里省略部分源码..........
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
三、使用Callable和Future创建线程
使用Callable创建线程和Runnable接口方式创建线程比较相似,不同的是,Callable接口提供了一个call() 方法作为线程执行体,而Runnable接口提供的是run()方法,同时,call()方法可以有返回值,而且需要用FutureTask类来包装Callable对象。
public interface Callable<V> {
V call() throws Exception;
}
步骤:
1、创建Callable接口的实现类,实现call() 方法
2、创建Callable实现类实例,通过FutureTask类来包装Callable对象,该对象封装了Callable对象的call()方法的返回值。
3、将创建的FutureTask对象作为target参数传入,创建Thread线程实例并启动新线程。
4、调用FutureTask对象的get方法获取返回值。
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 1;
return i;
}
public static void main(String[] args) {
CallableDemo demo = new CallableDemo();
FutureTask<Integer> task = new FutureTask<Integer>(demo);
new Thread(task).start();
try {
System.out.println("task 返回值为:" + task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行main方法后,程序输出如下结果:
task 返回值为:1
说明,task.get()确实返回了call() 方法的结果。那么其内部是怎么实现的呢。先打开FutureTask的构造方法,可以看到其内部是将Callable对象作为参数传递给当前实例的Callable成员,
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
同时,将成员变量state置为NEW,当启动task后,其run方法就会执行Callable的call()方法,
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//把call()的返回结果复制给result
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
//将结果设置给其他变量
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//把传过来的值赋值给outcome成员
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
run()方法中经过一系列的程序运行后,把call()的返回结果赋值给了outcome,然后当调用task.get()方法里获取的就是outcome的值了,这样一来,也就顺理成章的得到了返回结果。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
//返回outcome的值
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
可以看出,源码的运行逻辑还是比较清晰的,代码也比较容易理解,所以,我比较建议读者们有空可以多看看Java底层的源码,这样能帮助我们深入的理解功能是怎么实现的。
三种方式的对比
好了,创建线程的三种方式实例都说完了,接下来说下他们的对比。
从实现方式来说,使用Runnable接口和Callable接口的方式基本相同,区分的只是Callable实现的方法体可以有返回值,而继承Thread类是使用继承方式,所以,其实三种方法归为两类来分析即可。
1、使用继承Thread类的方式:
- 优势:编码简单,并且,当需要获取当前线程,可以直接用this
- 劣势:由于Java支持单继承,所以继承Thread后就不能继承其他父类
2、使用Runnable接口和Callable接口的方式:
- 优势:
比较灵活,线程只是实现接口,还可以继承其他父类。
这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
Callable接口的方式还能获取返回值。
- 劣势:
编码稍微复杂了点,需要创建更多对象。
如果想访问当前线程,需要用Thread.currentThread()方法。
总的来说,两种分类都有各自的优劣势,但其实后者的劣势相对优势来说不值一提,一般情况下,还是建议直接用接口的方式来创建线程,毕竟单一继承的劣势还是比较大的。
Java并发编程:Java创建线程的三种方式的更多相关文章
- 0036 Java学习笔记-多线程-创建线程的三种方式
创建线程 创建线程的三种方式: 继承java.lang.Thread 实现java.lang.Runnable接口 实现java.util.concurrent.Callable接口 所有的线程对象都 ...
- JAVA并发编程学习笔记------线程的三种创建方式
创建线程一般有如下几个方式: 1. 通过继承Thread类来创建一个线程: /** * 步骤1:定义一个继承Thread类的子类 * 步骤2:构造子类的一个对象 * 步骤3:启动线程: * */ pu ...
- java创建线程的三种方式及其对比
第一种方法:继承Thread类,重写run()方法,run()方法代表线程要执行的任务.第二种方法:实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务.第三种方法:实现c ...
- AJPFX总结java创建线程的三种方式及其对比
Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...
- java创建线程的三种方式及其对照
Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类.并重写该类的run方法,该run方法的方法体就代表了线程要完毕的任务.因此把run()方法称为运行 ...
- Java中创建线程的三种方式以及区别
在java中如果要创建线程的话,一般有3种方法: 继承Thread类: 实现Runnable接口: 使用Callable和Future创建线程. 1. 继承Thread类 继承Thread类的话,必须 ...
- python 并发编程 多线程 开启线程的两种方式
一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 二 开启线程的两种方式 第一种 每造一个进程,默认有一个线程,就是 ...
- Java多线程之创建线程的三种方式比较
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6560057.html 一:继承Thread类创建线程 1:继承Thread类定义线程子类: 2:重写run( ...
- Java创建线程的三种方式
一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实 ...
随机推荐
- visual studio 2017使用技巧
visual studio 2017使用技巧 批量删除代码中的空白行 Ctrl + H, 查找: ^(?([^\r\n])\s)*\r?$\r?\n 替换: 使用正则表达式 当前文档 常用快捷键 注释 ...
- 第四章 javascript的语句、对象笔记摘要
表达式语句 greeting ="Hello"+name;//赋值语句 i*=3; count++; delete o.x; //删除 alert(greeting); //函数 ...
- 【repost】javascript:;与javascript:void(0)使用介绍
有时候我们在编写js过程中,需要触发事件而不需要返回值,那么就可能需要这样的写法 最近看了好几个关于<a>标签和javascript:void(0)的帖子,谨记于此,以资查阅. 注:以下代 ...
- 《mysql必知必会》学习_第16章_20180807_欢
第16章:创建高级联结. P106 select concat(RTrim(vend_name),'(',RTrim(vend_country),')') as vend_title from ven ...
- 《mysql必知必会》学习_第14章_20180806_欢
第14章:使用子查询. 子查询是镶嵌在其他查询里面,相当其他的select查询的条件来. P91 select order_num from where prod_id='tnt2'; #检索条件 ...
- MFC控件的颜色设置
在绘制控件颜色时,控件会发送WM_CTLCOLOR消息给父窗口,父窗口收到消息后,映射到OnCtlColor()函数中处理. 该函数返回一个画刷用于设置子控件的背景颜色,子控件再执行自己的CtlCol ...
- Spring MVC 原理探秘 - 一个请求的旅行过程
1.简介 在前面的文章中,我较为详细的分析了 Spring IOC 和 AOP 部分的源码,并写成了文章.为了让我的 Spring 源码分析系列文章更为丰富一些,所以从本篇文章开始,我将来向大家介绍一 ...
- Javascript高级编程学习笔记(2)—— Script标签
script标签 js在浏览器中的使用,肯定会涉及到script标签. 那么script标签有哪些属性呢? 1.async:异步加载(不让页面等待该脚本的加载执行,异步加载页面的其他部分) 2.cha ...
- consul初步学习
简介 consul是一个服务发现框架 类似的还有zookeeper,eureka,etcd等 作用 服务发现(service discovery) 健康检查(health checking) 配置存储 ...
- 开源性能测试工具Locust使用篇(一)
1. 环境准备 安装python3.6 ,安装步骤略 pip install locust 安装完成后使用locust -V检查 2.locust使用,先编辑一个简单的load_test.py的脚本 ...