Fork me on GitHub

【Java多线程】Thread进程解析

Thread

进程和线程

进程

  • 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体
  • 进程一般由程序、数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志
  • 特征
    • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
    • 并发性:任何进程都可以同其他进程一起并发执行;
    • 独立性:进程是系统进行资源分配和调度的一个独立单位;
    • 结构性:进程由程序、数据和进程控制块三部分组成。

线程

  • 线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
  • 一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。
  • 一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。

区别:

  • 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。

线程生命周期

线程共包括以下5种状态。

1
2
3
4
5
6
7
8
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
  1. 新建状态(NEW) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
  2. 就绪状态(RUNNABLE): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
  3. 运行状态(RUNNING) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态(Blocked/*WAITING) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    • 等待阻塞 – 通过调用线程的wait()方法,让线程等待某工作的完成。
    • 同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
    • 其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  5. 死亡状态(TERMINATED) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

wait(),notify(),notifyAll()

  • notify() 唤醒在此对象监视器上等待的单个线程。

  • notifyAll() 唤醒在此对象监视器上等待的所有线程。

  • wait() 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。

    wait()的作用是让“当前线程”等待,“当前线程”是指当前在CPU上运行的线程

  • wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

  • wait(long timeout, int nanos) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个,所以notify(), wait()等函数定义在Object类

yield()

1
public static native void yield();
  • yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权
  • 并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
  • yield()不会释放所持有的对象锁,wait()方法会释放锁

sleep()

1
public static native void sleep(long millis) throws InterruptedException;
  • sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。
  • sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
  • sleep()不会释放所持有的对象锁

join()

join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) { //millis为0,进入死循环
wait(0);
}
} else {
while (isAlive()) {//millis 控制线程wait时间
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join(); //实际上是调用wait()方法等待,而wait()方法的对象是当前线程Father
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}

interrupt()

  • 中断这个线程

  • 除非当前线程中断自身,这是始终允许的,否则调用此线程的checkAccess方法,这可能会导致抛出SecurityException

  • 如果线程调用以下方法处于阻塞状态:

    Object类的方法wait()wait(long) ,或wait(long, int)

    或者Thread类的join()join(long)join(long, int)sleep(long) ,或sleep(long, int)

    那么调用interrupt()后它的中断标志true将被清除false,并且将收到一个InterruptedException

  • 如果线程在InterruptibleChannel 的I/O操作中阻塞,则设置线程的中断标志true,抛出ClosedByInterruptException

  • 如果线程在Selector选择器中阻塞,则设置线程的中断标志true,并且立即从选择器返回(非零值),就像调用了Selector.wakeup()方法一样

  • interrupted()isInterrupted()都能够用于检测对象的“中断标志”

    • interrupt() 设置线程的状态为“中断”状态
    • interrupted()除了返回中断标志之外,它还会重置中断标志;
    • isInterrupted()仅仅返回中断标志
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void interrupt() {
if (this != Thread.currentThread())
checkAccess(); //验证是否可进入

synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 仅仅设置中断标志
b.interrupt(this);
return;
}
}
interrupt0();
}

终止线程的方法:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void run() {
try {
// 1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) { //3. 可通过额外添加标志 volatile flag
// 执行任务...
}
} catch (InterruptedException ie) {
// 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}

创建线程方式

  • 继承Thread类,子类需重写run()方法,start()新起线程调用
    • start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。
    • run() : 可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
1
public class Thread implements Runnable
  • 实现Runnable接口,并实现run()方法
1
2
3
public interface Runnable {
public abstract void run();
}
  • 实现Callable接口,并实现call()方法,有返回值
1
2
3
public interface Callable<V> {
V call() throws Exception;
}
  • 线程池创建

参考

编程思想之多线程与多进程系列(上)

JDK8 之线程Thread小记

[Java多线程系列