前言

最近在看一些Java15的并发、线程调度以及一些实现方案的东西,虽然很多东西还是 1.5 的,但还是很有收获。

一、线程与任务

Java中,要用线程来执行任务,线程可以说是任务的容器。没有线程的物理开启(start0),就不会有任务被执行。

如果看过 Thread 源码就能知道,Java 对线程的实现是非常封闭的,其机制来源于c的低级的p线程方法。源码中,通过 native 关键字,依托于JNI接口,调用其他语言来实现对底层的访问。

二、Java线程与任务的基础接口

在 concurrent 和 lang 包中,首先有几个基础接口需要了解:

Runnable

Callable

Future

Runnable

对于 任务或线程 而言,最基本和初级的功能就是运行,所以在 Java1.0 的时候,只有Runnable接口。Runnable 接口的规范也非常简单:

public interface Runnable {

public abstract void run();

}

没有返回值也没有异常抛出,就是简简单单的执行,将任务执行的代码实现在 run 函数里就成。如果需要获得任务执行结果,必须在函数中写 回调函数(callback function)。

实现 Runnable 接口,根据实际业务需求,抽象出具有个性化、简单化的、需要新线程执行的并行任务。

Thread 是实现 Runnable 的一个实现类,所以我个人理解,在 Java 的视角,线程实际上是一个特殊的任务。

Runnable 适合作为一个被实现的接口被任务类实现(因为 Thread 与 Executor 只能输入 Runnable)。

Callable

但如果仅仅是这样,可不满足我们对于任务管理的要求。线程执行任务所抛出的任务异常(注意不是线程异常,两者本质区别),以及返回的结果,我们想要更方便的获取。于是,在 Java1.5 中就有了 Callable 接口。

Callable 接口规范也不复杂:

public interface Callable {

V call() throws Exception;

}

和 Runnable 比起来,我们可以看到明显的改变。首先在线程执行任务的过程中,我们可以 catch 到任务抛出的异常。其次,我们可以拿到输入类型的返回值。

Callable 更适合作为一个任务内容被写到任务中,因为可以在 run 中轻松处理抛出异常。(这点在FutureTask 中会有所体现)

Future

在 Java1.5 中还提供了 Future 接口,来对任务进行更详细的管理。

public interface Future {

boolean cancel(boolean mayInterruptIfRunning);

boolean isCancelled();

boolean isDone();

//阻塞(等待)获取计算结果

V get() throws InterruptedException, ExecutionException;

//超时报错

V get(long timeout, TimeUnit unit)

throws InterruptedException, ExecutionException, TimeoutException;

}

在这个接口规范中,我们可以对线程的状态进行监控。首先,提供了手动终结线程的规范。其次,比较好用的是,有了 get 函数,意味着我们可以在任意时间与地方(任意行),阻塞获取线程的计算结果。

三、Java线程与任务的基础实现类

在了解完这些基础的接口后,来看几个 Java 线程的实现类(Thread)与经典任务(FutureTask)的实现类,看看 Java 是怎么运用这些规范的。

Thread

Thread 就是 Java 中最简单、最直接,也是最底层创建线程对象,开启一个线程的类。

注意,一定要区别 run 函数和 start 函数。start 函数是物理开启一个线程,run 函数只是调用的我们对 Runnable 的实现(线程中执行的代码,即任务)。

//创建对象

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

//代码实现

//如果想让线程回传结果,只能在这里面写回调函数。

}

});

//开启线程

thread.start();

上面代码中,我们可以粗浅地理解为,将一个任务(Runnable 的实现)放到一个线程对象中,之后调用 start 函数让线程计算任务。

查看源码得知, Thread的构造器只能传入Runnable的实现。 所以,如果在不用线程池的情况下,在自己编写业务的任务类(Task)时,必须 implements Runnable。

我们看一下,上面提到的,实际开启线程的地方:

public synchronized void start() {

if (threadStatus != 0)

throw new IllegalThreadStateException();

group.add(this);

boolean started = false;

try {

//这里是真正物理层开启线程的地方 start0() 函数~~~

start0();

started = true;

} finally {

try {

if (!started) {

group.threadStartFailed(this);

}

} catch (Throwable ignore) {

/* do nothing. If start0 threw a Throwable then

it will be passed up the call stack */

}

}

}

private native void start0();

//仅仅是调用实现

@Override

public void run() {

if (target != null) {

target.run();

}

}

我们看到,真正在物理层开启线程的是 start() 中的 start0() 函数,就是上面说的JNI接口,调用其他语言来实现对底层的访问。也就是说,线程真正的被创建出来运行靠的是start()。

Runnable run 函数的实现,被位于 Thread 中的 run 函数调用,但是 Thread 的 run 是如何放到 start0 开启的线程中执行的,目前我还是不太清楚。需要接下来进一步的学习。

除了start0,还有很多操作是非 Java 实现的,比如:

private native void setPriority0(int newPriority);

private native void stop0(Object o);

private native void suspend0();

private native void resume0();

private native void interrupt0();

private static native void clearInterruptEvent();

private native void setNativeName(String name);

FutureTask

FutureTask 是对 Runnable 和 Future 的基本实现,实际就是对一个异步任务的基本管理,我们可以大致阅读一下其中的实现细节,为我们实现自己 Task 提供思路。

我们先看一下,这个类的实现继承关系:

在这里插入图片描述

清楚的看到,FutureTask 实际上是 Runnable 和 Future 的组合实现。(之前说的 Task 概念在这里也有所体现)。

private volatile int state;

private static final int NEW = 0;

private static final int COMPLETING = 1;

private static final int NORMAL = 2;

private static final int EXCEPTIONAL = 3;

private static final int CANCELLED = 4;

private static final int INTERRUPTING = 5;

private static final int INTERRUPTED = 6;

/** The underlying callable; nulled out after running */

private Callable callable;

/** The result to return or exception to throw from get() */

private Object outcome; // non-volatile, protected by state reads/writes

/** The thread running the callable; CASed during run() */

private volatile Thread runner;

/** Treiber stack of waiting threads */

private volatile WaitNode waiters;

在这个类中,可以看到任务具有的状态 state(任务的状态),包含一个 Callable (任务内容),输出结果(任意对象),以及 Thread(执行任务的Thread) 和 WaitNode(这个以后再提)。

当然,实际业务中,一个简单的 Task ,可能只需要有一个区别的 id 、判断执行的 handle 以及锁 lock 就能满足基础功能。

在 FutureTask 的构造器中,可以清晰地看到对象的初始化过程,以及这个类的构建本质。

public FutureTask(Callable callable) {

if (callable == null)

throw new NullPointerException();

this.callable = callable;

this.state = NEW;

}

public FutureTask(Runnable runnable, V result) {

//Executors 运用了 RunnableAdapter 将 Runnable 转为 Callable

this.callable = Executors.callable(runnable, result);

this.state = NEW;

}

可以看到,即使是用第二个构造器,在内部也把 Runnable 转化成了 Callable。

再观察一下其中的 run 函数:

public void run() {

if (state != NEW ||

!RUNNER.compareAndSet(this, null, Thread.currentThread()))

return;

try {

Callable c = callable;

if (c != null && state == NEW) {

V result;

boolean ran;

//这里就是为什么推荐使用Callable作为输入,因为方便catch异常,

//不然只能在 Runnable 的run中回调。

try {

//执行自己实现的call函数

result = c.call();

ran = true;

} catch (Throwable ex) {

result = null;

ran = false;

setException(ex);

}

if (ran)

set(result);

}

} finally {

runner = null;

int s = state;

if (s >= INTERRUPTING)

handlePossibleCancellationInterrupt(s);

}

}

我们可以看到最基本的,在中断条件中,任务不能被重复执行,本对象不能执行其他线程。之后也是正常的实现流程。

FutureTask 的使用方法也很简单,这里建议输入 Callable:

FutureTask f = new FutureTask<>(new Callable() {

@Override

public String call() throws Exception {

return null;

}

});

Thread t = new Thread(f);

t.start();

这里体现的也十分明显,把一个任务放入一个线程中去执行,并且获取任务的各种状态。

四、总结

在 Java 多线程的学习中,必须要理解线程与任务的区别、Runnable 与 Callable 的本质区别(不是代码上表象的),以及他们之间的联系。

Callable 更适合作为一个任务内容被写到任务中(因为可以在 run 中轻松处理抛出异常),Runnable 适合作为一个被实现的接口被任务类实现(因为 Thread 与 Executor 只能输入 Runnable)。 这点在 FutureTask 这个类中体现的淋漓尽致,再来体会一下:

为什么推荐 implements Runnable

//新建任务

FutureTask f = new FutureTask<>(new Callable() {

@Override

public String call() throws Exception {

return null;

}

});

//为什么推荐 implements Runnable

Thread t = new Thread(f);

//物理开启线程

t.start();

为什么推荐Callable输入到构造器。

public void run() {

if (state != NEW ||

!RUNNER.compareAndSet(this, null, Thread.currentThread()))

return;

try {

Callable c = callable;

if (c != null && state == NEW) {

V result;

boolean ran;

//这里就是为什么推荐使用Callable作为输入,因为方便catch异常,

//不然只能在 Runnable 的run中回调。

try {

//执行自己实现的call函数

result = c.call();

ran = true;

} catch (Throwable ex) {

result = null;

ran = false;

setException(ex);

}

if (ran)

set(result);

}

} finally {

runner = null;

int s = state;

if (s >= INTERRUPTING)

handlePossibleCancellationInterrupt(s);

}

}

要时刻记住, 线程是任务的容器,线程的物理启动和任务的实现代码是分开的。 这样,才能更深刻的理解 Java 多线程的本质,并且对于我们之后理解线程池是有帮助的。

java task和thread_【Java学习笔记-并发编程】线程与任务相关推荐

  1. go语言学习笔记 — 并发编程 — 通道channel(3):各种各样的通道

    3.1 单向通道 在声明通道时,我们可以设置只发送或只接收.这种被约束操作方向的通道称为单向通道. 声明单向通道 只发送:chan<-,只接收:<-chan var 通道实例 chan&l ...

  2. 拉勾网《32个Java面试必考点》学习笔记之十二------架构演进与容器技术

    本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...

  3. 拉勾网《32个Java面试必考点》学习笔记之一------Java职业发展路径

    本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...

  4. Java开发面试高频考点学习笔记(每日更新)

    Java开发面试高频考点学习笔记(每日更新) 1.深拷贝和浅拷贝 2.接口和抽象类的区别 3.java的内存是怎么分配的 4.java中的泛型是什么?类型擦除是什么? 5.Java中的反射是什么 6. ...

  5. 杨晓峰-java核心技术36讲(学习笔记)- 第1讲 | 谈谈你对Java平台的理解?

    杨晓峰-java核心技术36讲(学习笔记) 接下来我会分享杨晓峰-java核心技术36讲的学习笔记,内容较多,补充了其中一些牛人评论,相对详细(仅供个人学习记录整理,希望大家支持正版:https:// ...

  6. Java虚拟机(JVM)学习笔记(不定时更新)

    Java虚拟机(JVM)学习笔记 不少组织都曾开发过Java虚拟机: SUN公司曾经使用过3个虚拟机,Classic.Exact VM.Hotspot.     其中Hotspot虚拟机沿用至今,并已 ...

  7. 拉勾网《32个Java面试必考点》学习笔记之十一------消息队列与数据库

    本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...

  8. Java程序猿的JavaScript学习笔记(12——jQuery-扩展选择器)

    计划按例如以下顺序完毕这篇笔记: Java程序猿的JavaScript学习笔记(1--理念) Java程序猿的JavaScript学习笔记(2--属性复制和继承) Java程序猿的JavaScript ...

  9. Java程序猿的JavaScript学习笔记(汇总文件夹)

    最终完结了,历时半个月. 内容包含: JavaScript面向对象特性分析,JavaScript高手必经之路. jQuery源代码级解析. jQuery EasyUI源代码级解析. Java程序猿的J ...

最新文章

  1. 靠云计算翻身的微软正在“挑衅”亚马逊AWS
  2. SQL基础---SQL WHERE 子句
  3. 【采用】【风险管理】金融业务风控相关框架(宝藏)
  4. 谭浩强课后题(数组篇)
  5. gentoo 安装时的网络配置
  6. 信息学奥赛一本通C++语言——1049:晶晶赴约会
  7. 800乘600的分辨率_600元能买到啥配置的手机?
  8. ajax用iframe,使用ajax Post请求更新iframe内容
  9. 整理python笔记001(列表(深浅copy),元祖,字典,集合)
  10. 微型计算机就是完全采用大规模集成电路,微型计算机就是完全采用大规模集成电路或超大规模集成电路芯片,使计算机的主要电路都集成在一块芯片上。...
  11. (附源码)springboot校园兼职系统 毕业设计 031122
  12. PMSG类型究竟是什么意思?
  13. 东北大学 数据库概论 第三章SQL 习题见解:Find all customers who have an account at all branches located in Brooklyn
  14. python中columns用法_pandas中DataFrame修改index、columns名的方法示例
  15. srand(time(0))函数的用法介绍
  16. 华为OD机试 - 最长的顺子
  17. golang端口重用
  18. oracle 创建 temporary tablespace group
  19. 扩展433兆赫射频发射模块的传输范围
  20. anaconda创建虚拟环境Solving environment: failed

热门文章

  1. 历经十年,Windows 7 生命周期将结束,微软:快换 Windows 10
  2. springboot事物注解不生效_springboot事务不生效的几种解决方案
  3. java中forward和redirect_好程序员Java教程分享:Forward和Redirect的区别
  4. python需要缩进的块_“需要缩进块”
  5. 编码设置过滤的注意点 2021-04-27
  6. java 实现nfa的化简_NFA的实现
  7. go去掉最后一个字符_可维护的Go代码程序指南(一)之变量篇
  8. 最大子段和动态规划_动态规划解决最大正方形问题
  9. git撤销单个文件的修改_大牛总结的 Git 使用技巧,写得太好了
  10. python退出帮助系统_Python基础(09):帮助