深刻理解Java并发编程之线程Thread

前言

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

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

线程优先级

  1. 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;
复制代码
  1. 线程优先级不能做为程序正确性的依赖,由于操做系统能够彻底不用理会Java线程对于优先级的设定。

线程的状态

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

  1. NEW: 初始转态,线程被构建,可是尚未调用start()方法。
  2. RUNNABLE: 正在执行的线程状态,JVM中runnable线程状态对应于操做系统中的就绪和运行两种状态。
  3. BLOCKED: 线程等待monitor互斥量的阻塞状态,在blocked状态的线程正在被执行Object.wait()后等着进入或者再次同步块或者同步方法。
  4. WAITING: 等待状态,下列方法会致使线程处于等待状态:
    • Object.wait with no timeout
    • Thread.join with on timeout
    • LockSupport.park
  5. TIMED_WAITING: 超时等待,超过等待时间便会自动返回运行状态,下列方法会致使线程处于超时等待状态:
    • Thread.sleep
    • Object.wait(long) with timeout
    • Thread.join(long) with timeout
    • LockSupport.parkNanos
    • LockSupport.parkUntil
  6. TERMINATED: 线程完成执行后结束的状态。

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

Monitor

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

在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个参数:并发

  1. _Owner: 用于指向ObjectMonito对象的线程
  2. _EntrySet:用来保存处于blocked状态的线程列表
  3. _WaitSet: 用来保存处于waiting状态的线程
  4. _count: 计数器

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

Dump文件中的线程状态

  1. Deadlock:死锁线程,通常指多个线程调用间,进入相互资源占用,致使一直等待没法释放的状况。ui

  2. Runnable:通常指该线程正在执行状态中,该线程占用了资源 。spa

  3. Waiting on condition:等待资源,或等待某个条件的发生 。操作系统

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

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

  6. In Object.wait(): 当线程得到了 Monitor,进入了临界区以后,若是发现线程继续运行的条件没有知足,它则调用对象(通常就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。

线程同步时的执行模型

  1. 任意一个对象都拥有本身的Monitor;
  2. 当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法;
  3. 没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

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

从图中能够看到

  1. 任意线程对Object(Object由synchronized保护)的访问,首先要得到Object的监视器。
  2. 若是获取成功,线程得到monitor,进入同步区域,执行同步块。
  3. 若是获取失败,线程进入同步队列,线程状态变为BLOCKED。
  4. 当访问Object的前驱(得到了锁的线程)释放了锁,则该释放操做唤醒阻塞在同步队列中的线程,使其从新尝试对监视器的获取。

线程等待/通知机制

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

等待/通知的相关方法是任意Java对象都具有的,由于这些方法被定义在全部对象的超类java.lang.Object上,方法和描述以下:

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

  2. notifyAll(): 通知全部等待在该对象上的线程。

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

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

  5. wait(long, int): 超时状态更细粒度的控制,能够达到纳秒。

注意

  1. wait(), notify(), notifyAll()方法的调用都须要位于被synchronized关键字包裹的代码块或者是方法中,即线程须要获取锁才能执行这些方法
  2. wait() - notify(), wait() - notifyAll()的使用须要针对同一个锁,否则会抛出异常。
  3. 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
  4. notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,须要调用notify()或notifAll()的线程释放锁以后,等待线程才有机会从wait()返回
  5. notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中全部的线程所有移到同步队列,被移动的线程状态由WAITING变为BLOCKED
  6. 从wait()方法返回的前提是得到了调用对象的锁

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

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

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

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

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

等待/通知的经典范式

等待/通知的经典范式分为两个部分:等待方和通知方。 等待方遵循以下原则:

  1. 获取对象的锁。
  2. 若是条件不知足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件知足则执行对应的逻辑。 对应的伪代码以下:
synchronized(对象) {
	while(条件不知足) {
		对象.wait();
	}
	对应的处理逻辑
}
复制代码

通知方遵循以下原则:

  1. 得到对象的锁。
  2. 改变条件。
  3. 通知全部等待在对象上的线程。 对应的伪代码以下:
synchronized(对象) {
	改变条件
	对象.notifyAll();
}
复制代码

Thread.join()

这部份内容能够参见深刻理解Java并发编程之经过JDK C++源码以及Debug源码死扣Thread.join()

ThreadLocal

这部份内容能够参见深刻理解Java并发编程之把ThreadLocal扣烂

参考与感谢

  1. blog.csdn.net/lengxiao199…
  2. blog.csdn.net/javazejian/…
  3. www.cnblogs.com/zhengyun_us…
相关文章
相关标签/搜索