引言

在日常开发工作中,多线程开发可以说是必备技能,好的程序员是一定要对线程这块有深入了解的,我是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创建线程的三种方式的更多相关文章

  1. 0036 Java学习笔记-多线程-创建线程的三种方式

    创建线程 创建线程的三种方式: 继承java.lang.Thread 实现java.lang.Runnable接口 实现java.util.concurrent.Callable接口 所有的线程对象都 ...

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

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

  3. java创建线程的三种方式及其对比

    第一种方法:继承Thread类,重写run()方法,run()方法代表线程要执行的任务.第二种方法:实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务.第三种方法:实现c ...

  4. AJPFX总结java创建线程的三种方式及其对比

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...

  5. java创建线程的三种方式及其对照

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类.并重写该类的run方法,该run方法的方法体就代表了线程要完毕的任务.因此把run()方法称为运行 ...

  6. Java中创建线程的三种方式以及区别

    在java中如果要创建线程的话,一般有3种方法: 继承Thread类: 实现Runnable接口: 使用Callable和Future创建线程. 1. 继承Thread类 继承Thread类的话,必须 ...

  7. python 并发编程 多线程 开启线程的两种方式

    一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性 二 开启线程的两种方式 第一种 每造一个进程,默认有一个线程,就是 ...

  8. Java多线程之创建线程的三种方式比较

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6560057.html  一:继承Thread类创建线程 1:继承Thread类定义线程子类: 2:重写run( ...

  9. Java创建线程的三种方式

    一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实 ...

随机推荐

  1. wordpress添加文章固定字段

    让wordpress的文章数据表 增加一个字段,使其能在文章编辑页能编辑,并能通过rest api 获取出来. 例:给文章加一个缩略图字段 litpic 首先 通过mysql 给文章表 wp_post ...

  2. Html5与Css3知识点拾遗(五)

    css3更新的颜色 RGBA:红.绿.蓝.不透明度 rgba(89,0,127,0.4); HSL和HSLA:色相.饱和度.亮度.不透明度 hsl(282,100%,25%); hsl(282,100 ...

  3. Integer包装类源码分析

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 今天上班的途中在手机里看到几道关于Integer拆装箱的小题目,正好有点时间翻看了一下Integer类的源码,加上自己的一点思考,决定写点 ...

  4. java相关技术问答(一)

    网上一些没有标准答案的面试题,我自己做的解答总结,有任何异议可以提出来~^_^,不断更新中... Springboot除了自动配置与传统的spring还有哪些不同 传统的springweb项目需要部署 ...

  5. PyTorch(二)Intermediate

    Convolutional Neural Network import torch import torch.nn as nn import torchvision import torchvisio ...

  6. javascript编程中this解析

    一.为什么要使用this? this 提供了一种更优雅的方式来隐式"传递"一个对象引用,因此可以将 API 设计得更加简洁并且易于复用.随着你的使用模式越来越复杂,显式传递上下文对 ...

  7. Liferay的一些应用领域

    Liferay的用途是快速的部署内外站点,统一权限管理,开发Web热插拔插件,并不是所有系统都适合 不适合Liferay的一些应用领域: 1.独立认证.简单的系统,比如一些简单的增删改查:2.复杂业务 ...

  8. EF code first,set composite primary key 复合key问题

    环境: EF core 2.0 Net core 2.0 错误: 因实体定义了多个key,打开数据库时程序报以下错误 An unhandled exception occurred while pro ...

  9. redis 乐观锁实践秒杀

    需求:有一个标(理解成抢红包也行,accountBalance预赋值1000元),大家可以抢购,每个用户抢购成功后,更新最后标的总数,在并发情况下,使用redis的乐观锁,保证更新标总值正确性,先往r ...

  10. Atom编辑器中安装Emmet插件失败的问题

    今天使用Atom编辑器安装Emmet插件的时候,刚开始的时候报错: 报错内容如下: Installing “emmet@2.4.3” failed.Show output… Compiler tool ...