java thread wait参数_深入理解Java并发编程之线程Thread

前言

现代操作系统在运行一个程序时,会为其创建一个进程。例如,启动一个Java程序,操作系统就会创建一个Java进程。现代操作系统调度的最小单元是线程,也叫轻量级进程(Light Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。

Java线程通过调用线程的start()方法进行启动,随着run()方法的执行完毕,线程也随之终止。

线程优先级

Java线程优先级从低到高为1~10,默认优先级为5。如下为Thread.java的源码:

/**

* The minimum priority that a thread can have.

*/

public final static int MIN_PRIORITY = 1;

/**

* The default priority that is assigned to a thread.

*/

public final static int NORM_PRIORITY = 5;

/**

* The maximum priority that a thread can have.

*/

public final static int MAX_PRIORITY = 10;

复制代码线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定。

线程的状态

Java线程在运行的生命周期中可能处于6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。如下内容截取JDK 1.8 Thread.java的源码:

NEW: 初始转态,线程被构建,但是还没有调用start()方法。

RUNNABLE: 正在执行的线程状态,JVM中runnable线程状态对应于操作系统中的就绪和运行两种状态。

BLOCKED: 线程等待monitor互斥量的阻塞状态,在blocked状态的线程正在被执行Object.wait()后等着进入或者再次同步块或者同步方法。

WAITING: 等待状态,下列方法会导致线程处于等待状态:

Object.wait with no timeout

Thread.join with on timeout

LockSupport.park

TIMED_WAITING: 超时等待,超过等待时间便会自动返回运行状态,下列方法会导致线程处于超时等待状态:

Thread.sleep

Object.wait(long) with timeout

Thread.join(long) with timeout

LockSupport.parkNanos

LockSupport.parkUntil

TERMINATED: 线程完成执行后结束的状态。

jstack Dump日志文件中的线程状态

Monitor

Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象的锁。每一个对象都有,也仅有一个 monitor。

在HotSpot JVM中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):

ObjectMonitor() {

_header = NULL;

_count = 0; //记录个数

_waiters = 0,

_recursions = 0;

_object = NULL;

_owner = NULL;

_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet

_WaitSetLock = 0 ;

_Responsible = NULL ;

_succ = NULL ;

_cxq = NULL ;

FreeNext = NULL ;

_EntryList = NULL ; //处于等block状态的线程,会被加入到该列表

_SpinFreq = 0 ;

_SpinClock = 0 ;

OwnerIsThread = 0 ;

}

复制代码

ObjectMonitor中主要有以下4个参数:

_Owner: 用于指向ObjectMonito对象的线程

_EntrySet:用来保存处于blocked状态的线程列表

_WaitSet: 用来保存处于waiting状态的线程

_count: 计数器

当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程。同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 _WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示:

java thread wait参数_深入理解Java并发编程之线程Thread_第1张图片

Dump文件中的线程状态

Deadlock:死锁线程,一般指多个线程调用间,进入相互资源占用,导致一直等待无法释放的情况。

Runnable:一般指该线程正在执行状态中,该线程占用了资源 。

Waiting on condition:等待资源,或等待某个条件的发生 。

Blocked:线程阻塞,是指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。

Waiting for monitor entry :在线程尝试执行同步代码前,在monitor的”Entry Set“队列中的等待线程。

In Object.wait(): 当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。

线程同步时的执行模型

任意一个对象都拥有自己的Monitor;

当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法;

没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

下图描述了对象、对象的监视器、同步队列和执行线程之间的关系:

java thread wait参数_深入理解Java并发编程之线程Thread_第2张图片

从图中可以看到

任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。

如果获取成功,线程获得monitor,进入同步区域,执行同步块。

如果获取失败,线程进入同步队列,线程状态变为BLOCKED。

当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。

线程等待/通知机制

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法和描述如下:

notify(): 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。

notifyAll(): 通知所有等待在该对象上的线程。

wait(): 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。

wait(long): 超时等待一段时间,这里单位是毫秒。

wait(long, int): 超时状态更细粒度的控制,可以达到纳秒。

注意:

wait(), notify(), notifyAll()方法的调用都需要位于被synchronized关键字包裹的代码块或者是方法中,即线程需要获取锁才能执行这些方法。

wait() - notify(), wait() - notifyAll()的使用需要针对同一个锁,不然会抛出异常。

调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。

notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。

notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。

从wait()方法返回的前提是获得了调用对象的锁。

从上述可以发现,等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。

下面是上述示例的过程图:

java thread wait参数_深入理解Java并发编程之线程Thread_第3张图片

WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入Waiting状态。

由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为Blocked状态。

NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。

等待/通知的经典范式

等待/通知的经典范式分为两个部分:等待方和通知方。

等待方遵循如下原则:

获取对象的锁。

如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

条件满足则执行对应的逻辑。

对应的伪代码如下:

synchronized(对象) {

while(条件不满足) {

对象.wait();

}

对应的处理逻辑

}

复制代码

通知方遵循如下原则:

获得对象的锁。

改变条件。

通知所有等待在对象上的线程。

对应的伪代码如下:

synchronized(对象) {

改变条件

对象.notifyAll();

}

复制代码

Thread.join()

ThreadLocal

参考与感谢

你可能感兴趣的:(java,thread,wait参数)