线程简介

1. 什么是线程

现代操作系统在运行程序时,会为其创建一个进程。比如你现在用的浏览器,系统就会为其创建一个甚至多个进程。引入进程的目的是为了更好的使操作系统并发执行程序。来提高资源利用率和系统吞吐量,增加并发程度。
线程是现代操作系统调度的基本单元,是运行在进程上下文中的逻辑流,是程序执行流的最小单元,也叫轻量级进程(Light Weight Process),也可以称之为CPU的执行单元。一个程序作为一个进程来运行,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行一个处理器核心上。引入线程,则是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。线程不拥有系统资源(代码,数据、堆、共享库、文件),只拥有运行中必不可少的资源(线程ID, 栈,栈指针,程序计数器、通用目的寄存器和条件码)。
线程是调度的抽象。Java中将线程的执行和执行对象抽象开来,JDK包中执行的有Thread类,Executor框架,可执行目标有Runaable,Callable。

1.1 线程的优点

  1. 多处理器使用: 提高资源利用率和系统吞吐量。

1.2 线程的风险

  1. 安全风险:对于共享变量的访问、操作容易造成安全风险。
  2. 活跃度风险:死锁引发活动度失败。
  3. 性能风险:线程之间频繁调度导致上下文切换导致巨大的系统开销。

2. 线程的状态

ThreadStatus
图片来自:Java并发编程的艺术-

  • 新建(NEW):用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(READY)。

注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。

  • 运行状态(RUNNABLE):Java线程将操作系统中的就绪和运行两种状态笼统的称作“运行中”。

  • 就绪(READY):处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。

  • 运行(RUNNING):处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 等待状态(WAITING):表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程作出一些特定动作(通知或中断)。

  • 超时等待状态(TIME_WAITING):该状态不同于WAITING,它是可以在指定的时间自行返回的。

  • 阻塞(BLOCKED):javase7 docs关于BLOCKED的描述:Thread state for a thread blocked waiting for a monitor lock. A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait.翻译:阻塞线程是等待获取同步监视器锁的线程。线程等待获取同步监视器锁进入同步代码块/方法,或者调用Object.wait方法后,被唤醒后等待获取同步监视器锁,重入同步代码块/方法。

  • 死亡(DEAD):当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

3. 线程优先级

每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。

每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。

注:虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。

4. Daemon线程

Daemon线程是一种支持型线程,因为它主要被用做程序中后台调度及支持性工作。
注意:构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。守护线程的用途为:

1
2
• 守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。
• Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。

1
2
3
4
5
6
7
8
9
10
11
12
13
 /**
* 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
* 该方法必须在启动线程前调用。
* 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。
* 这可能抛出 SecurityException(在当前线程中)。
* 参数:
* on - 如果为 true,则将该线程标记为守护线程。
* 抛出:
* IllegalThreadStateException - 如果该线程处于活动状态。
* SecurityException - 如果当前线程无法修改该线程。
*/

public final void setDaemon(boolean on)

5. 引用

Java多线程详解
Java并发编程艺术
javase7 docs
Java线程(1)-读Thread类源码