Java线程生命周期之旅

java线程生命周期

  • 关于线程生命周期的不同状态,java5以后,线程状态已内部枚举类的方式明确定义在java.lang.Thread.State
  • 1.新建(new),表示该线程刚被创建还未真正启动,可认为其是java内部状态
  • 2.就绪(RUNNABLE),表示该线程已经在JVM中执行,由于执行需要资源,他可能处于正运行,也可能是等待系统分配给它CPU片段,在就绪队列里面
  • 3.在其他一些分析中,会有额外区分一种状态RUNNING,但从JAVA API(内部线程状态枚举类)来说,并不能变现处理
  • 4.阻塞(BLOCKED),这个状态和同步相关,表示线程在等待获取Monitor lock来锁住资源供自己使用。如线程通过synchronized区获取某个锁,但是其他线程已经独占,那么该线程就处于阻塞。
  • 5.等待(Waiting),表示正在等待期限线程采取某些操作。场景如生产者消费者模式,发现任务条件不满足,就让消费者线程等待(Wait),然后生产者线程准备数据,输出结果,然后通过notify等动作,通知消费者可以继续工作。Thread.join()也会令线程进入WAITING.
  • 6.计时等待(TIMED_WAIT),其进入条件和等待状态类似,但是调用的是存在超时条件的方法,如wait或join等方法的指定超时版本。如下
    public final native void wait(long timeout) throws InterruptedException;
  • 7.终止(TERMINATED),不管意外退出还是正常执行结束,线程已经完成。也叫死亡。

若线程被调用两次start()方法

  • java线程不允许启动两次,第二次比如抛出运行时IllegalThreadStateException,多次调用会被认为编译错误
  • 第二次调用start()方法时,线程可能处于终止或其他(非new)状态。不论如何,都不可以再次被启动

知识扩展

线程

  • 从OS角度,线程是系统调度最小单元,一个进程包含多个线程
  • 作为任务真正运作者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是回合进程内其他线程共享FileDescriptor、虚拟地址空间等
  • 具体实现,分为内核线程、用户线程,java线程实现与JVM相关。如最熟悉的Sun/Oracle JDK,其线程经历演进,java1.2后,jdk抛弃所谓Green Thread(用户调度的线程),现在的模型是一对一映射到OS内核线程

    Thread源码

  • 基本操作逻辑以JNI(java native interface)调用的本地代码
    Runnable task = () -> {System.out.println("Hello World!");};
    Thread myThread = new Thread(task);
    myThread.start();
    myThread.join();
    
  • 上述实现利弊
    优点:得益于java精细粒度的线程和相关并发操作,胜任构建高扩展大型应用。
    缺点:其复杂性也提高了并发编程的门槛。
    
  • 线程基本操作
    1.创建、启动线程,执行join方法等待结束,Runnable不会受java不支持类多继承的限制,重用代码实现,提高代码复用。
    Runnable task = () -> {System.out.println("Hello World!");};
    Thread myThread = new Thread(task);
    myThread.start();
    myThread.join();
    2.将上述逻辑start()和join()改成如下结构,提交task线程任务,线程创建、管理交由线程池,也能利用Future等机制更好处理执行结果。
    Future future = Executors.newFixedThreadPool(1).submit(task).get();
    

影响线程状态因素

  • 线程自身方法,除start(),还有多个join方法,等待线程结束;yield是告知调度器,主动让出CPU;另外,就是一些标记过时的resume、stop、suspend等,为了destroy、stop会被移除
  • 基于Object提供的基础方法(wait/notify/notifyAll).如果我们持有某个对象的Monitor锁,调用wait会让当前线程处于WAITTING状态,直到其他线程notify或者notifyAll,本质上是提供Monitor的获取和释放的能力,是基本线程间通信方式
  • 并发类库的工具,如CountDownLatch.await()会让当前线程进入等待状态,指定latch被基数为0,这可以看做线程间通信的Signal
  • 线程状态转换与方法直接的对应图
    java线程状态与方法对应关系图

API使用

  • 守护线程(Daemon Thread),有时应用需要一个长期驻留的服务程序,不希望其影响应用退出,就可以将其设置为守护线程,如果jvm发现只有守护线程存在,将结束进程。
    注意:必须在线程启动前设置
    Thread daemonThread = new Thread();
    daemonThread.setDaemon(true);
    daemonThread.start();
    
  • Spurious wakeup ,多核CPU系统中,线程等待存在一种肯,即在无任何线程广播或发出信号情况下,线程被唤醒,如处理不当就会出现诡异并发问题,所以在在等待条件过程中,建议采用如下模式来写
    // 推荐
    while ( isCondition()) {
    waitForAConfition(...);
    }
    
    // 不推荐,可能引入bug
    if ( isCondition()) {
    waitForAConfition(...);
    }
    
  • Thread.opSpinWait,java9 中引入特性,用于自旋锁,自旋锁(spin-wait,busy-waitng,未获得锁,一直处于空转),也可以认为岂不是一种锁,而是一个针对短期等待的性能优化技术。“onSpinWait()”无任何行为保证,而是对jvm的暗示,jvm可能会利用CPU的pause指令进一步提高性能,性能敏感应用可以关注。
  • 慎用ThreadLLocal(本地存储),java提高保存线程私有的机制,在整个线程生命周期有效,方便地在一个线程关联的不同业务模块之间传递信息,如事务id,Cookie等上下文相关信息。其实现结构如下,数据存储于ThreadLocalMap,内部为弱引用
    1.当key为null时,该条目为”废弃条目”,相关”value”的回收,往往依赖于几个关键点,即set,remove,rehash
    static class ThreadLocalMap {
      static class Entry extends WeakReference<ThreadLocal<?>> {
          /** The value associated with this ThreadLocal. */
          Object value;
          Entry(ThreadLocal<?> k, Object v) {
              super(k);
          value = v;
          }
          }
       // …
    }
    2.set示例,具体逻辑在cleanSomeSlots 和 expungeStaleEntry 之中
    private void set(ThreadLocal<?> key, Object value) {
      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);
    
      for (Entry e = tab[i];; …) {
          //…
          if (k == null) {
    // 替换废弃条目
              replaceStaleEntry(key, value, i);
              return;
          }
           }
    
      tab[i] = new Entry(key, value);
      int sz = ++size;
    //  扫描并清理发现的废弃条目,并检查容量是否超限
      if (!cleanSomeSlots(i, sz) && sz >= threshold)
          rehash();// 清理废弃条目,如果仍然超限,则扩容(加倍)
    }  
    
  • 结合java引用类型,发现一个特别地方,通常弱引用都会和引用队列配合清理机制,但是ThreadLocal(本地存储),是个例外。这意味废弃项目回收依赖于显式地触发,否则就要等线程结束,进而回收相应的ThreadLocalMap.这也是很多OOM来源。因此谨慎使用ThreadLocal,在使用后一定要自己负责remove,并且不要和线程池配合,因为worker线程往往不会退出,仍然会造成OOM.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值