Java多线程-线程状态

概述

最近开始强迫自己养成学习做笔记的习惯,今天重新复习了一下JAVA线程的状态,记录一下自己学习到的东西

JAVA线程状态

根据自己的理解,绘画了一张图来描述线程的状态变化



下面结合线程状态变化图进行简单讲解:

  • 调用start()方法,创建线程,等待获得CPU。
  • 执行同步块,多个线程竞争锁,没有获得锁的线程阻塞,等待锁的释放
  • 调用Thread.sleep() 方法,线程有限期等待(如果该线程之前获得锁,锁不会被释放)
  • 调用wait()方法,线程无限期等待,直到其他线程唤醒,当线程调用wait()方法时,线程进入一个阻塞队列,在后面会具体分析
  • 当线程退出run()方法,线程结束,关于线程的停止在下面的小节会详细讲解。

深入wait()

前面说调用wait()方法以后,线程会进入一个阻塞队列,接下来我们一步步剖析源码去了解。
查看JDK源码,发现该方法在Object类中定义,是一个实例方法(Java中采用对象锁):

public final void wait() throws InterruptedException {
        wait(0);
}

wait()方法调用了wait(0),该方法是一个native方法:

//The current thread must own this object's monitor.(调用该方法前,必须用于对象锁)
public final native void wait(long timeout) throws InterruptedException;

查看OpenJDK源码,openjdk\jdk\src\share\native\java\lang\Object.c

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

可以看到wait 对应的是 native 方法是JVM_MonitorWait, 继续查看JVM_MonitorWait的实现:openjdk\hotspot\src\share\vm\prims\jvm.cpp

VM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
  JVMWrapper("JVM_MonitorWait");
  Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
  assert(obj->is_instance() || obj->is_array(), "JVM_MonitorWait must apply to an object");
  JavaThreadInObjectWaitState jtiows(thread, ms != 0);
  if (JvmtiExport::should_post_monitor_wait()) {
    JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
  }
  ObjectSynchronizer::wait(obj, ms, THREAD);

在该方法中调用了 ObjectSynchronizer::wait(obj, ms, THREAD)方法,具体实现如下:

void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  //是否采用偏向锁
  if (UseBiasedLocking) {
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  //如果wait time <0,抛出异常
  if (millis < 0) {
    TEVENT (wait - throw IAX) ;
    THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
  }
  //获取ObjectMonitor,调用wait()方法
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  monitor->wait(millis, true, THREAD);

  /* This dummy call is in place to get around dtrace bug 6254741.  Once
     that's fixed we can uncomment the following line and remove the call */
  // DTRACE_MONITOR_PROBE(waited, monitor, obj(), THREAD);
  dtrace_waited_probe(monitor, obj, THREAD);
}

发现最终是调用ObjectMonitor得wait()方法将线程加入阻塞队列,ObjectMonitor类定义源码位置:openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp,下面是ObjectMonitor类的部分定义(抽取了重要属性):

  friend class ObjectSynchronizer;
  friend class ObjectWaiter;
  volatile markOop   _header;       // 对象头
  void*     volatile _object;       // backward object pointer - strong root
  void *  volatile _owner;          // 该锁的拥有着
  volatile jlong _previous_owner_tid; //  上一个拥护该锁的线程ID
  ObjectWaiter * volatile _EntryList ;     // 等待锁释放的线程
  volatile intptr_t  _waiters;      // 等待线程的数量
  ObjectWaiter * volatile _WaitSet; // 等待锁的线程列表,调用wait()方法,里面的线程需要等待notify()方法的调用

在ObjectMonitor中定义了_WaitSet属性,是ObjectWaiter类型的一个指针,该类表示等待锁的线程,具体的定义如下:

class ObjectWaiter : public StackObj {
 public:
  enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
  enum Sorted  { PREPEND, APPEND, SORTED } ;
  ObjectWaiter * volatile _next; //前一个ObjectWaiter
  ObjectWaiter * volatile _prev;//后一个ObjectWaiter
  Thread*       _thread; //等待的线程
  jlong         _notifier_tid;
  ParkEvent *   _event;
  volatile int  _notified ;
  volatile TStates TState ;
  Sorted        _Sorted ;           // List placement disposition
  bool          _active ;           // Contention monitoring is enabled
 public:
  ObjectWaiter(Thread* thread);

  void wait_reenter_begin(ObjectMonitor *mon);
  void wait_reenter_end(ObjectMonitor *mon);
};

通过查看ObjectWaiter类的定义,得知ObjectMonitor通过一个双向链表来保存等待该锁的线程。当调用Object.wait()方法时,在JVM中会调用ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS)方法,下面时方法的具体实现:

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
  ...................
   // 创建ObjectWaiter,添加到_WaitSet队列中
   ObjectWaiter node(Self);
   node.TState = ObjectWaiter::TS_WAIT ;
   Self->_ParkEvent->reset() ;
   OrderAccess::fence();          // ST into Event; membar ; LD interrupted-flag

 //WaitSetLock保护等待队列。通常只锁的拥有着才能访问等待队列
   Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
 //加入等待队列,等待队列是循环双链表
   AddWaiter (&node) ;
   //使用的是一个简单的自旋锁
   Thread::SpinRelease (&_WaitSetLock) ;
   .....................
}

调用wait()方法以后线程进入等待队列,在其他线程中调用notify方法或者notifyAll()唤醒等待的队列。在JVM中,通过调用void ObjectMonitor::notify(TRAPS) 方法唤醒等待的线程。由于篇幅问题具体代码,请自行查看JDK源码。

结束线程

线程的结束其实就是结束run()方法的调用,具体有三种方案。

方案一: 通过判断变量的值结束线程,参考代码如下:

public class ThreadExample extends Thread {
    private volatile boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            System.out.println(".....");
        }
        System.out.println("sub thread end....");
    }
    public static void main(String[] args) throws InterruptedException {
        ThreadExample example = new ThreadExample();
        example.start();
        Thread.sleep(1000 * 3);
        System.out.println("asking thread stop.....");
        example.flag = false;
        Thread.sleep(1000 * 3);
        System.out.println("main thread stop......");
    }
}

在该程序中,通过判断一个boolean类型的变量来决定是否跳出while循环,结束run()方法的调用。

方案二: Thread.interrupt()

调用Thread实例的 interrupt() 方法,接下来该方法会调用

 // Just to set the interrupt flag(该方法只是设置一个中断标志)
private native void interrupt0();

所以说, interrupt() 方法不会真正的中断一个线程,这是实在一个中断标志,在线程中判断线程标志,结束run()方法的调用。参考代码如下:

public class ThreadExample2 extends Thread {
    public static void main(String[] args) throws InterruptedException {
        ThreadExample2 test = new ThreadExample2();
        test.start();
        Thread.sleep(1000 * 3);
        test.interrupt();
        Thread.sleep(1000 * 3);
        System.out.println("main thread end .....");
    }
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println(".......");
        }
        System.out.println("sub thread end.....");
    }
}

方案三:Thread.interrupt()+变量的值判断

如果线程已经调用了Thread.sleep(),wait()方法导致线程阻塞,线程不会去判断变量的值中断线程,在阻塞状态调用 interrupt() 会抛出一个中断信号,使得线程退出阻塞,同时清除中断标志。可以结合:Thread.interrupt()+变量的值判断实现线程的停止,参考代码如下:

class ThreadExample3 extends Thread {
    volatile boolean stop = false;
    public static void main(String args[]) throws Exception {
        ThreadExample3 thread = new ThreadExample3();
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        /*
         * 如果线程阻塞,将不会检查此变量,调用interrupt之后,线程就可以尽早的终结被阻 塞状 态,能够检查这一变量。
         */
        thread.stop = true;
        /*
         * 这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退 出阻 塞的状态
         */
        thread.interrupt();
        Thread.sleep(3000);
        System.out.println("main thread end...");
        System.exit(0);
    }
    public void run() {
        while (!stop) {
            System.out.println("...........");
            try {
                Thread.sleep(1000 * 10);
            } catch (InterruptedException e) {
                // 接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态
                System.out.println("Thread interrupted...");
            }
        }
        if(Thread.currentThread().isInterrupted()) {
            System.out.println("-------有中断标志---------");
        }else {
            System.out.println("-------无中断标志---------");
        }
        System.out.println("Thread exiting under request...");
    }
}

Ok,今天的总结完毕,跑路。。。。。。。。。
参考:http://blog.csdn.net/lirenzuo/article/details/75911548

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,454评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,957评论 1 18
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,350评论 3 87
  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,730评论 12 45
  • 1 不管是男孩子还是女孩子,都对自己朋友的另一半远一点,这是最起码的尊重,就算是真的没有什么,但是在别人眼里呢。总...
    666清水阅读 271评论 0 0