JDK - Thread源码介绍

概述

深入的介绍了JDK源码中,Thread对象的几个常用细节点,以加深对其内部原理的认识。夯实读者Java基础知识。

分析

  • 基本介绍
    Thread是Java中重要的一个类,它是实现多线程的重要成员,也是多线程的开启者。每一个线程都有优先级,优先级高的线程通常是先于优先级低的线程执行。
    每一个线程可以被标注成守护线程(Java中线程分为守护线程和非守护线程,当非守护线程执行完毕后,只剩下守护线程时,虚拟机会退出应用)。默认情况下,开启的线程
    为非守护线程。线程的开启为start方法,开启线程后,执行的代码为run方法,需要override run方法。有两种实现线程的方式,一是:继承Thread类,
    另一种是实现Runnable接口,例如:

    class PrimeThread extends Thread {
      long minPrime;
      PrimeThread(long minPrime) {
        this.minPrime = minPrime;
      }
    
      public void run() {
        // compute primes larger than minPrime
             . . .
      }
    }
    
    PrimeThread p = new PrimeThread(143);
     p.start();
    class PrimeRun implements Runnable {
      long minPrime;
      PrimeRun(long minPrime) {
        this.minPrime = minPrime;
      }
    
      public void run() {
        // compute primes larger than minPrime
              . . .
      }
    }
    
    PrimeRun p = new PrimeRun(143);
     new Thread(p).start();
    

    新启一个线程执行其run()方法,一个线程只能start一次。主要是通过调用native start0()来实现。run()方法是不需要用户来调用的,
    当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。\
    每个线程都有自己的名字,多个线程可以有相同的名字。

    public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
    
        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }
    
    /**
     * Returns this thread's name.
     *
     * @return  this thread's name.
     * @see     #setName(String)
     */
    public final String getName() {
        return name;
    }

    可以改变或获取线程的名称

  • 主要介绍

    1.线程优先级

     /**
      * 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,最高为10,默认优先级为5. 优先级的设置和获取如下:

    public final void setPriority(int newPriority) {
         ThreadGroup g;
         checkAccess();
         if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
             throw new IllegalArgumentException();
         }
         if((g = getThreadGroup()) != null) {
             if (newPriority > g.getMaxPriority()) {
                 newPriority = g.getMaxPriority();
             }
             setPriority0(priority = newPriority);
         }
     }
    
     /**
      * Returns this thread's priority.
      *
      * @return  this thread's priority.
      * @see     #setPriority
      */
     public final int getPriority() {
         return priority;
     }

    2.线程状态

    public enum State {
      NEW,
      RUNNABLE,
      BLOCKED,
      WAITING,
      TIMED_WAITING,
      TERMINATED;
    }

    分别是创建状态(未开始)、运行状态、线程阻塞状态(阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
    通常由Object.wait发起,目的等待重新获取锁执行同步代码块)、线程等待状态(可以有三种方式实现:
    Object.wait、Thread.join和LockSupport.park,一个线程的等待状态,是为了等待另一个线程执行某种特别的操作。Object.wait是等待其他线程执行
    Object.notify()或者Object.notifyAll()去唤醒,Thread.join()目的是等待线程执行完毕)、定时等待状态(可以有:Thread.sleep、Object.wait(long)
    Thread.join(long)、LockSupport.parkNanos和LockSupport.parkUntil实现。)和线程结束状态(执行完毕)。

    public State getState() {
      // get current thread state
      return sun.misc.VM.toThreadState(threadStatus);
    }

    需要注意的是:\
    阻塞:当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。
    它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。\

    阻塞的情况分三种: \
    等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 \
    同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 \
    其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、
    join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。\

    等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,
    可响应中断。例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。\
    需要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,
    而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但话又说回来,虽然等锁时进入的状态不一样,
    但被唤醒后又都进入runnable态,从行为效果来看又是一样的。\
    sleep方法:sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,
    则即使调用sleep方法,其他线程也无法访问这个对象。\
    yield方法:调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。
    但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会,
    调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。\
    join方法:join()、join(long millis)和join(long millis,int nanoseconds)。join()实际是利用了wait(),
    只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。\

    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()) {
              wait(0);
          }
      } else {
          while (isAlive()) {
              long delay = millis - now;
              if (delay <= 0) {
                  break;
              }
              wait(delay);
              now = System.currentTimeMillis() - base;
          }
      }
    }
    public final synchronized void join(long millis, int nanos)
    throws InterruptedException {
    
      if (millis < 0) {
          throw new IllegalArgumentException("timeout value is negative");
      }
    
      if (nanos < 0 || nanos > 999999) {
          throw new IllegalArgumentException(
                              "nanosecond timeout value out of range");
      }
    
      if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
          millis++;
      }
    
      join(millis);
    }
    public final void join() throws InterruptedException {
          join(0);
    }
    public final native void wait(long timeout) throws InterruptedException;

    3.守护线程和非守护线程的设置

    public final void setDaemon(boolean on) {
      checkAccess();
      if (isAlive()) {
          throw new IllegalThreadStateException();
      }
      daemon = on;
    }
    
    /**
    * Tests if this thread is a daemon thread.
    *
    * @return  <code>true</code> if this thread is a daemon thread;
    *          <code>false</code> otherwise.
    * @see     #setDaemon(boolean)
    */
    public final boolean isDaemon() {
      return daemon;
    }

    其中void setDaemon(boolean on)必须在线程start之前调用。否则会抛出IllegalThreadStateException异常

    4.线程组

    public synchronized void start() {
      /**
       * This method is not invoked for the main method thread or "system"
       * group threads created/set up by the VM. Any new functionality added
       * to this method in the future may have to also be added to the VM.
       *
       * A zero status value corresponds to state "NEW".
       */
      if (threadStatus != 0)
          throw new IllegalThreadStateException();
    
      /* Notify the group that this thread is about to be started
       * so that it can be added to the group's list of threads
       * and the group's unstarted count can be decremented. */
      group.add(this);
    
      boolean started = false;
      try {
          start0();
          started = true;
      } finally {
          try {
              if (!started) {
                  group.threadStartFailed(this);
              }
          } catch (Throwable ignore) {
              /* do nothing. If start0 threw a Throwable then
                it will be passed up the call stack */
          }
      }
    }
    
    private native void start0();

    线程组在开启线程时将当前线程加入组中。

    5.其他注意点

    • stop()可以立即将一个线程终止,非常方便。但是stop被废弃,原因是stop方法太暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。
    • 线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦!至于目标线程接到通知后如何处理,则完全由目标线程自行
      决定。这点很重要,如果中断后,线程立即无条件退出,就会遇到stop同样的问题。

      public void interrupt() // 线程中断
      public boolean isInterrupted() // 判断是否被中断
      public static boolean interrupted() // 判断是否被中断,并清除当前中断状态
    • 挂起(suspend)和继续执行(resume)线程:这两个操作是一对相反的操作,被挂起的线程,必须要等到resume后,才能执行。他们也是被废弃的方法。

    • 不推荐suspend是因为suspend在导致线程暂停的同时,并不会去释放任何资源。此时,其他任何线程想要访问被他暂用的锁时,都会被牵连,导致无法
      正常执行。直到对应的线程进行了resume操作,线程才能继续执行。但是,如果resume操作意外地在suspend之前执行了,那么背挂起的线程可能很难有机会
      被继续执行。更重要的是它的锁不会释放,因此可能导致整个系统工作不正常。而且,对于被挂起的线程,从它的状态上看,居然是runnable。

总结

  1. 线程状态
    线程有五个状态:new一个线程对象后,首先进入创建状态,执行Thread#start方法之后,进入就绪状态,
    线程调度程序将就绪状态的线程标记为运行状态并真正运行。线程运行过程中,可以挂起,进入阻塞状态,阻塞状态恢复后,接着进入就绪而不是立马又恢复运行状态,
    线程执行完了,就进入结束/死亡状态。
  2. Thread使用注意
    线程执行的业务逻辑,放在run()方法中,使用 thread.start() 启动线程。
    wait方法需要和notify方法配套使用;
    守护线程必须在线程启动之前设置;
    如果需要等待线程执行完毕,可以调用 join()方法。
阅读更多

没有更多推荐了,返回首页