多线程编程是开发者必须要掌握的基本技能,线程(Thread)是基础和核心。只有深刻地理解Java线程,才能写出合理、高效的多线程代码。本文将研究Java中的线程,同时会捎带部分操作系统相关内容。主要的内容如下:
- 进程与线程
- Java线程(线程创建、
Thread
中的主要方法、线程通信)
进程与线程
进程与线程,是操作系统中的重要概念,稍微有点计算机基础的开发者都会听说过、接触过。
进程(Process):计算机中的程序关于某数据集合上的一次运行活动,是操作系统结构的基础(来自百度百科)。狭义上来说,进程是正在运行的程序实例。进程和程序间的关系很微妙,用一个比喻(来自《现代操作系统》)来说明。有一位拥有一手好厨艺计算机科学家正在为女儿烘制蛋糕,他有制作蛋糕的食谱,还有一些原料(面粉、鸡蛋等)。在这个例子中,食谱就是程序(用适当形式描述的算法),科学家就是CPU,原料就是各种输入数据。那么,进程就是食谱、原料以及烘制蛋糕的系列动作的总和。
线程(Thread):有时也被称为轻量级进程,是程序调度和执行的最小单元。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个进程可以包含多个线程,而一个线程只能属于唯一的进程。
已经有了进程,为什么还会需要线程呢?主要原因如下:
- 许多应用程序中,同时发生着多个活动。将这些应用程序分解成多个准并行的线程,程序设计的模型会变成更加简单。
- 由于线程比进程进行更加轻量,创建和取消更加容易。
- 如果程序是IO密集型,那么多线程执行能够加快程序的执行速度。(如果是CPU密集型,则没有这个优势)
- 在多CPU系统中,多线程是可以真正并行执行的。
Java线程
线程状态
- 新建状态(New):线程对象已经创建,还没有在其上调用start()方法。
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
创建和启动线程
Java中有两种方法定义线程:
1、继承java.lang.Thread
类。
/**
* 进程Thread类定义线程。
* @author xialei
* @version 1.0 2016年8月1日下午9:22:37
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello");
}
}
上面的代码继承了java.lang.Thread
类,然后重写了run()
方法。该方法的方法体内是线程需要完成的任务,称为线程执行体。下面的代码启动这个线程:
MyThread myThread = new MyThread();
myThread.start();
通过调用Thread
类中的start()
方法启动线程,这方法最终会调用start0()
native方法启动线程。调用start()
方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。
2、实现java.lang.Runnable
接口。
/**
* 实现Runnable接口定义线程。
* @author xialei
* @version 1.0 2016年8月1日下午9:28:28
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello");
}
}
调用下面的代码可以启动线程。
Thread thread = new Thread(new MyRunnable());
thread.start();
MyRunnable
类本身并不能启动线程,而是需要实例化一个Thread
类来启动线程。可以看到,不管是哪种方法,都是调用Thread
的start()
方法来启动的。Thread
类和Runnable
接口到底是什么关系呢?实际上,Thread
类本身就实现了Runnable
接口,Thread
中的run()
就是实现了Runnable
中的run()
方法,其代码如下:
public void run() {
if (target != null) {
target.run();
}
}
通过Thread(Runnable)
构造方法可传入Runnable
对象,然后直接调用其run()
方法。
线程类型
Java中的线程有用户(User)线程和守护(Daemon)线程两类。我们默认创建的线程是用户(User)线程,守护线程–也称“服务线程”,在没有用户线程可服务时会自动离开。只要当前JVM实例中尚存在任何存货的用户线程,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
下面来分别演示一下这两类线程。
public class UserThreadTest {
public static void main(String[] args) {
Thread t &