• Java线程生命周期
  • Java线程实现方法
    • 继承Thread类,重写run()方法
    • 实现Runnable接口,便于继承其他类
    • Callable类替换Runnable类,实现返回值
    • Future接口对任务进行监测
    • FutureTask类:Future类的唯一实现
  • Java多线程的调度
    • Java多线程的就绪,运行和死亡
    • Java线程的阻塞
      • Java线程的阻塞方法
    • 后台线程
    • 线程的优先级
    • 线程让步yield()
  • 参考文章

前面几篇文章为Java多线程做足了铺垫,这篇终于到了正题,一起学习一下Java多线程的基础知识。

1. Java线程生命周期

下图表述了Java线程的几个基本状态:

  • New新建状态:线程创建后即进入。
  • Runnable就绪状态:调用线程的start方法后,线程不会立刻运行,而是进入就绪状态,等待CPU调度。
  • Running运行状态: CPU开始调度处于就绪状态的线程后,线程进入运行状态。换言之,就绪状态是运行状态的唯一入口。
  • Blocked阻塞状态:线程由于某种原因放弃CPU的使用权,停止执行,此时进入阻塞状态,一直到进入就绪态CPU才能对其进行重新调度。
    产生Blocked状态的几种可能
  1. 等待阻塞:运行线程中的wait()方法,使本地线程进入阻塞状态。

  2. 同步阻塞:线程获取sychronized同步锁失败,进入同步阻塞状态。

  3. 其他阻塞:调用线程的sleep()或join()发出I/O请求,线程进入阻塞状态。当sleep()超时,join()等待线程终止或超时,处理完I/O时,线程重新进入就绪状态。

  • Dead死亡状态:线程执行完毕或异常退出。该线程结束了生命周期。

2. Java线程实现方法

2.1. 继承Thread类,重写run()方法

最基本的线程方式,通过继承Thread类,重写run()方法即可运行子线程。缺点是Java类只能有一个父类,如果所要使用的类本身有其他需要继承的实体,就会比较麻烦。

class MyThread extends Thread {private volatile boolean isRunning = true;public boolean isRunning() {return isRunning;}public void setRunning(boolean isRunning) {this.isRunning = isRunning;}@Overridepublic void run() {System.out.println("进入到run方法中了");while (isRunning == true) {}System.out.println("线程执行完成了");//结束线程,进入dead态}
}
public class RunThread{public static void main(String[] args) {try {MyThread thread = new MyThread();//创建线程,进入new态thread.start();//start线程,进入runnable态,CPU开始调度后进入running态Thread.sleep(1000);//阻塞主线程,进入blocked态thread.setRunning(false);} catch (InterruptedException e) {e.printStackTrace();}}
}

2.2. 实现Runnable接口,便于继承其他类

针对Thread类继承可能存在困难的问题,利用Java接口无限制的特性,利用接口来进行线程实现。在运行时,需要运行一个thread并把接入了Runnable的类以参数形式传给thread。

class MyClass implements Runnable {private volatile boolean isRunning = true;public boolean isRunning() {return isRunning;}public void setRunning(boolean isRunning) {this.isRunning = isRunning;}@Overridepublic void run() {System.out.println("进入到run方法中了");while (isRunning == true) {}System.out.println("线程执行完成了");}
}
public class RunThread{public static void main(String[] args) {try {MyClass myClass=new MyClass();Thread thread=new Thread(myClass);thread.start();Thread.sleep(1000);myClass.setRunning(false);} catch (InterruptedException e) {e.printStackTrace();}}
}

我们需要知道的是,Thread类本身就已经接入了Runnable接口,如果我们给一个自定义的已经重写run的Thread类的子类A传入一个接入Runnable的类B作为参数,那么该子类A会运行类B的run()函数,原因为,Thread中的run()函数代码如下:

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

可见,如果target存在,即有Runnable类的子类作为参数传递,则不运行Thread提供的run()方法。

最基本的Thread和Runnable有一个共同的问题,即是run()函数没有返回值,导致线程间交互会比较复杂,需要依赖引用的互相传递。因此,当需要有返回值时,Java提供了另外的类。

2.3. Callable类替换Runnable类,实现返回值

Callable接口定义如下:

public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

对于Callablel类我们只需要知道以下几点

  1. 需要实现重写其call()函数来实现功能。

  2. 泛型的输入类型即是call()的返回类型。

实现了call函数,简单的把Callable接口替换Runnable接口,我们就可以实现简答的有返回值线程了,但Java提供了更多的线程状态检查,控制类来更好的使用这两个接口。

2.4. Future接口对任务进行监测

Future接口有5个方法,我们依次来进行介绍

public interface Future<V> {boolean cancel(boolean mayInterruptIfRunning);boolean isCancelled();boolean isDone();V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
  • cancel() 方法来表示取消任务,如果取消成功则返回true,否则返回false。其参数mayInterruptIfRunning则表示正在执行的任务。
  • isCanelled() 方法表示任务是否已经取消,如果任务完成前成功取消则返回true。
  • isDone() 方法表示任务是否已经完成,如果完成返回true。
  • get() 方法获取执行结果,这个方法会产生阻塞,一直到执行完毕才返回。
  • get(long timeout, TimeUnit unit) 方法在获取执行结果同时给一个时间,如果超时仍被阻塞则返回Null。

2.5. FutureTask类:Future类的唯一实现

FutureTask类实现了RunnableFuture接口,而RunnableFuture接口如同名字一样,接入了Runnable和Future两个类。因此FutrueTask既可以作为Runnable被线程执行(自动调用其run()方法),同时又可以视为Future类来得到Callable类的返回值。此处我们可以看到Java设置这个类的用意——让用户尽量使用这个类去管理线程。

使用FutureTask类托管Callable的一个例子:

class MyClass implements Callable {private volatile boolean isRunning = true;public boolean isRunning() {return isRunning;}public void setRunning(boolean isRunning) {this.isRunning = isRunning;}@Overridepublic Object call() {System.out.println("进入到run方法中了");while (isRunning == true) {}System.out.println("线程执行完成了");return isRunning;}
}
public class RunThread{public static void main(String[] args) {try {MyClass myClass=new MyClass();FutureTask futureTask=new FutureTask(myClass);Thread thread=new Thread(futureTask);thread.start();Thread.sleep(1000);myClass.setRunning(false);try{System.out.println(futureTask.get());}catch (Exception e){System.out.println("Type error");}} catch (InterruptedException e) {e.printStackTrace();}}
}

3. Java多线程的调度

3.1. Java多线程的就绪,运行和死亡

如图,需要注意的是CPU调度线程有随机性,不排除线程调用yield()进入就绪状态后CPU又去调用线程的情况。

3.2. Java线程的阻塞

3.2.1. Java线程的阻塞方法

  • join()方法:让一个线程等待另一个线程完成才继续执行。如,在线程A的运行过程中调用线程B的join方法,则线程A被阻塞,等待线程B运行完成后才继续运行。该方法通常用于主线程中。
public static void main(String[] args){......threadB.join();
}
  • sleep()方法:让当前的线程进入阻塞状态,暂停指定时间,之后恢复就绪状态。例如,在前面的例子里,我们让主线程sleep 1000毫秒以显示效果。需要注意的是,sleep()方法并不会释放锁,实际上,sleep()是一个与实例无关的方法。sleep()方法是一个静态方法,也就是说他只对当前线程有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,其效果和Thread.sleep()无异但造成了一定迷惑性。sleep()的目的在于让当前线程自主的暂停,因而是针对当前线程生效的静态方法,即是在子线程的run()函数中也可以调用。如果不是这样的设计,sleep()要么和wait()同质,要么会造成严重的死锁。与sleep()方法有一定共性但实质上区别很大的wait()方法接下来我会单开文章去谈。

3.3. 后台线程

后台线程主要为其他线程提供服务,或“守护线程”。例如:JVM的GC线程。后台线程与前台线程生命周期有一定关联。主要体现在:当所有前台线程死去时,后台线程也会自动死去。

设置一个线程是否为后台线程只需要调用该线程的setDaemon(boolean val)方法即可,创建一个线程后,默认为非后台线程。

public static void main(String[] args){......threadB.setDaemon(true);
}

3.4. 线程的优先级

考虑到下面的情景,5个线程竞争同一资源,资源目前被重要线程A锁定,当线程A执行完毕,解锁释放资源后,我们希望重要线程E能先于BCD得到资源。此时,我们则需要优先级这个属性。每个线程都有一个优先级变量,该变量的值可以是1到10之间的一个常数。数字越大,代表优先级越高。Java给了其中三个常量名称,分别是:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

默认情况下,一个线程的优先级是其父线程的优先级。而主线程的优先级是NORM_PRIORITY,即数字5,因此,我们可以看到很多资料里认为现成的默认优先级是5,其实不是这样的。我们可以通过调用线程的setPrority(short val)来设置线程的优先级以及使用getPrority()来获取优先级。

class MyClass implements Callable {@Overridepublic Object call() {System.out.println("线程执行完成了,将返回其子线程的优先级");return new Thread().getPriority();}
}public class RunThread {public static void main(String[] args) {FutureTask futureTask = new FutureTask(new MyClass());Thread thread = new Thread(futureTask);thread.setPriority(3);System.out.println("设置该线程的优先级为"+thread.getPriority());thread.start();try {System.out.println("该线程的子线程优先级为" + futureTask.get());} catch (Exception e) {System.out.println("Type error");}}
}

上面这段代码的返回内容如下:

设置该线程的优先级为3
线程执行完成了,将返回其子线程的优先级
该线程的子线程优先级为3

然而,需要注意的是,优先级无法保证线程的执行顺序,即在BCDE竞争资源的情境中,无法保证E线程一定先执行。优先级高则其获取CPU资源的可能性比较大,而不是一定会先于其他线程获取执行优先权,优先级低的线程仍有得到执行权的可能。

同时我们不要忘记,Java的Thread类的核心方法是native方法,即是线程实现大量依赖宿主机的机制。Java有10个优先级,如果宿主机是Linux系统,有31个优先级,显然没什么问题。但如果宿主机是Windows7,只有7个优先级,那么Java的10个优先级就会根据系统的优先级进行映射。同时,线程的优先级也会受到虚拟机的影响。

3.5. 线程让步yield()

前面多少也提到过了。yield()和sleep()类似,是静态方法,作用于将当前运行的线程,其作用于具体线程,调用方法是Thread.yield(),使用t.yield()同样是能够使用但逻辑不清晰的做法。具体来说。yield()是让当前线程从running态回到runnable态。之后,线程间重新进行资源竞争,由CPU进行调度,因此yield()实际上是一个与锁不是很有关系的方法,和sleep()一样,作为当前线程的主动方法,不会引起死锁。

4. 参考文章

Java总结篇系列:Java多线程

Java并发编程:Callable,Future和FutureTask

[线程的状态、分类及优先级](http://blog.csdn.net/yegongheng/article/details/38708765

转载于:https://www.cnblogs.com/cielosun/p/6655679.html

Java多线程:生命周期,实现与调度相关推荐

  1. java多线程生命周期

    在java多线程中,没有主次线程的区别,只要有线程还在运行,进程就不会结束 import java.util.Arrays; import java.util.HashSet; import java ...

  2. Java 对象生命周期和类生命周期

    Java 对象生命周期 在JVM运行空间中,对象的整个生命周期大致可以分为7个阶段:创建阶段(Creation).应用阶段(Using).不可视阶段(Invisible).不可到达阶段(Unreach ...

  3. java对象生命周期_Java对象生命周期和类生命周期

    原标题:Java对象生命周期和类生命周期 作者:彭空空 链接:https://www.jianshu.com/p/25ea857ba78b 导读 对象的生命周期 类的加载机制 类的生命周期 类加载器 ...

  4. java resume过时方法_面试官没想到,一个 Java 线程生命周期,我可以扯半小时

    面试官:你不是精通 Java 并发吗?从基础的 Java 线程生命周期开始讲讲吧. 好的,面试官.吧啦啦啦... 如果要说 Java 线程的生命周期的话,那我觉得就要先说说操作系统的线程生命周期 因为 ...

  5. Java 对象生命周期

    Java 对象生命周期 一直对Java对象的实例化.对象.对象的引用.堆 栈存放的内容迷惑不解.看了 Java编程思想,理解似乎又深了一层. 对象和对象的引用 Java 编程思想中,把对象的引用比喻成 ...

  6. 详解Java线程生命周期与状态切换

    前提 最近有点懒散,没什么比较有深度的产出.刚好想重新研读一下JUC线程池的源码实现,在此之前先深入了解一下Java中的线程实现,包括线程的生命周期.状态切换以及线程的上下文切换等等.编写本文的时候, ...

  7. Java线程生命周期与状态切换

    前提# 最近有点懒散,没什么比较有深度的产出.刚好想重新研读一下JUC线程池的源码实现,在此之前先深入了解一下Java中的线程实现,包括线程的生命周期.状态切换以及线程的上下文切换等等.编写本文的时候 ...

  8. java方法生命周期_Java线程的第二种实现方式以及生命周期

    上篇中我们了解了Java线程的第一种实现方式,主要分两步,第一步是继承java.lang.Thread; 第二步是重写run()方法.接下来我们来看Java线程的第二种实现方式,也是分为两步,第一步, ...

  9. 回炉再造-多线程生命周期

    五种状态:新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)5种状态. 1. 新建状态,当程序使用new关键字创建了一个线程之后,该线程就处于新 ...

  10. java main生命周期_Java从入门到入土(62)线程的生命周期

    线程是程序内部的一个顺序控制流,他具有一个特定的生命周期.在一个线程的生命周期中,他总是处于某一种状态中.线程的状态表示了线程正在进行的活动以及在这段时间内线程能完成的任务. 线程的生命周期包括五个状 ...

最新文章

  1. 从JoinBatchGroup 代码细节 来看Rocksdb的相比于leveldb的写入优势
  2. 安防业内人士对云存储未来的发展充满信心
  3. python精要(80)-wxpython(2)-helloworld
  4. c# 弹性和瞬态故障处理库Polly
  5. 深入react技术栈(9):表单
  6. 十二、ubuntu20.10(Linux)下Pycharm配置pyqt5开发环境
  7. matlab程序 surf算法,【求大神帮忙,surf算法源代码解析】
  8. java基础之-I/O流和File类解析
  9. C#实现TreeView向XML的绝对转换类
  10. 高仿精仿快播应用android源码下载
  11. Spring事务@Transactional注解原理
  12. 详解数据库的第一范式、第二范式、第三范式、BCNF范式
  13. Spring之FactoryBean的使用与源码解析
  14. 阿里云服务器怎么预防CC攻击?
  15. C++报错illegal instruction
  16. 搭建在线网校平台的三个好处
  17. YbtOJ 洛谷UVA10559 方块消除
  18. UE4 材质 UV膨胀技术
  19. 开源OA:手把手教你搭建OA办公系统(3)开发企业报销审批流程
  20. java实现随机数抽奖_JAVA使用随机数实现概率抽奖

热门文章

  1. 谈谈如何在面试中发掘程序猿的核心竞争力 什么是程序员的核心竞争力?
  2. MKNetwork网络请求过程中onCompletion调用两次的问题
  3. html控件的id和name属性有什么不同
  4. ASP.NET 2.0中母版页中引用文件路径的问题(收集)
  5. 还在用ViT的16x16 Patch分割方法吗?中科院自动化所提出Deformable Patch-based方法,涨点显著!...
  6. 告别只会调参和调包,全球顶会论文审稿人带你7天玩转图像分割
  7. 图像编辑新作:连续色彩迁移
  8. 零基础转行学习python是否还在纠结?这里告诉你答案!
  9. ImageNet又被Long-Short Transformer 霸榜!
  10. 土是独体字结构吗_毛笔楷书基础练习独体字部首的写法(左部)4