前面几篇文章为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;
}
@Override
public 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;
}
@Override
public 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()函数代码如下:

  @Override
public 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;
}
@Override
public 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 {
@Override
public 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

Java多线程:生命周期,实现与调度的更多相关文章

  1. java类生命周期详细解析

    (一)详解java类的生命周期 引言 最近有位细心的朋友在阅读笔者的文章时,对java类的生命周期问题有一些疑惑,笔者打开百度搜了一下相关的问题,看到网上的资料很少有把这个问题讲明白的,主要是因为目前 ...

  2. Java线程生命周期

    当你需要使用Java线程在多线程环境下进行编程时,理解Java的线程周期与线程的状态是非常重要的.通过实现Runnale接口或者继承Thread类,我们可以创建线程,为了启动一个线程,我们需要创建一个 ...

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

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

  4. Java实现生命周期管理机制

    先扯再说 最近一直在研究某个国产开源的MySQL数据库中间件,拉下其最新版的代码到eclipse后,启动起来,然后做各种测试和代码追踪:用完想要关闭它时,拉出它的STOP类想要运行时,发现这个类里赫然 ...

  5. java类型生命周期

    开始阶段 装载:把二进制形式的java类型读入jvm中. 1)通过该类型的完全限定名,产生一个代表该类型的二进制数据流:2)解析这个二进制数据流为方法区内的内部数据结构:3)创建一个表示该类型的jav ...

  6. java对象生命周期概述复习

    最近看了下java对象的生命周期做个笔记复习复习,很多不同的原因会使一个java类被初始化,可能造成类初始化的操作: 1)  创建一个java类的实例对象. 2)  调用一个java类中的静态方法. ...

  7. Java 线程生命周期

    |作者:RexFang |出处:http://www.cnblogs.com/rexfang/ |关于作者:Java 程序员一枚 |版权:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此 ...

  8. java类生命周期,类的“加载,连接,初始化,使用,卸载过程”详解

    “ 如果说核心类库的 API 比做数学公式的话,那么 Java 虚拟机的知识就好比公式的推导过程” 每本Java入门书籍在介绍Java这门语言的时候都会提到Java跨平台,“一次解释,到处运行的特点“ ...

  9. java线程生命周期及其对应方法

    http://blog.sina.com.cn/s/blog_a8aa82cc0101ktrm.html http://wenku.baidu.com/view/c242df69011ca300a6c ...

  10. Java初始化生命周期

    package com.init; abstract class Glyph { void draw() { System.out.println("Glyph.draw()"); ...

随机推荐

  1. 【Django】优化小技巧之清除过期session

    事情是这样的,大概也就几万注册用户的站点(使用django1.6), session 存储在关系型数据库,这次上线之后发现session表几十万数据了,过期session没有被自动删除 思考 官网 s ...

  2. Swift开发UITableView常用的一些细节知识点介绍

    <code class="objectivec"><strong><span style="font-size:18px;"> ...

  3. [WinForm]动态显示本地目录图片与悬浮窗

    加载显示: if (File.Exists(@"D:\产品图片\" + item + ".jpg")) { //需要判断是否存在图片 Image img = I ...

  4. Gradle 1.12用户指南翻译——第二十九章. Checkstyle 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

  5. C++继承中的public/protected/private

    今天杨老师讲到C++的继承的时候用一个表来说明子类继承父类后访问权限的变化,如下表: 注:在本类中,protected与private是相同的,但protected可以被继承,而private却不行. ...

  6. lambda表达式初步

    // Lambda_test20140801.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <algorithm& ...

  7. java--GUI(图形用户接口)

    转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9795435 day22 01-GUI(概述) GUI(图形用户界面) 1. GUI(Griph ...

  8. DB Query Analyzer 5.02 is distributed, 53 articles concerned have been published

    DB Query Analyzer is presented by Master Gen feng, Ma from Chinese Mainland. It has English version ...

  9. CoolBlog开发笔记第5课:请求与响应

    教程目录 1.1 CoolBlog开发笔记第1课:项目分析 1.2 CoolBlog开发笔记第2课:搭建开发环境 1.3 CoolBlog开发笔记第3课:创建Django应用 1.4 CoolBlog ...

  10. oracle 修改 字段名称

    暂时应该没有对应的方法,所以我用自己想好的方法去修改 /*修改原字段名name为name_tmp,是将想改名称的字段改为没用/临时的字段*/ Alter  table 表名 rename column ...