Изучите «Thread» с точки зрения исходного кода

Java

предисловие

JavaПоток в используетThreadкласс реализован,Threadдля начинающихJavaЯ изучил его в молодости и использовал на практике, но я никогда не видел его реализации с точки зрения исходного кода, сегодня я снова изучу его с точки зрения исходного кода.Java Thread, может позжеThreadпрактика удобнее.

Начните с заметки

кажется, я читалJDKИзучающие исходный код могут почувствоватьJDKВ исходном коде есть очень подробные комментарии. При чтении исходного кода определенного класса вы должны сначала посмотреть введение комментария. Исходный текст комментария не будет опубликован. Ниже приводится мое резюме:

  • Threadпоток выполнения в программе,JavaВиртуальные машины позволяют приложениям разрешить несколько потоков выполнения одновременно.

  • Каждый поток имеет понятие приоритета, поток с более высоким приоритетом выполняется в приоритете перед потоком с более низким приоритетом.

  • Каждый поток может быть установлен как поток демона

  • Когда код, работающий в потоке, создает новыйThreadобъект, приоритет нового потока такой же, как у потока создания

  • когдаJavaКогда виртуальная машина запустится, она запуститmainпоток, у него нет потока демона,mainПоток будет продолжать выполняться до тех пор, пока не будут отправлены следующие условия

    • Runtimeметод выхода из классаexitвызывается, и менеджер безопасности разрешает операцию выхода
    • все потоки, не являющиеся демонами, умерли, илиrunВыполнение метода завершается и возвращает результат в обычном режиме, илиrunметод выдает исключение
  • Первый способ создания потока: наследованиеThreadкласс, переопределитьrunметод

    //定义线程类
    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();
    
  • Второй способ создания потока: реализацияRunnableинтерфейс, переопределениеrunметод, потому чтоJavaОграничение единственного наследования

    //定义线程
     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();
    
  • При создании треда вы можете указать имя для треда, если не указать, то для него будет автоматически сгенерировано имя

  • Если не указано иное,nullпараметр, переданныйThreadКонструктор или метод в классе вызовет бросокNullPointerException

Общие свойства потока

прочитать одинJavaclass, начнем с того, какими свойствами он обладает:

//线程名称,创建线程时可以指定线程的名称
private volatile String name;

//线程优先级,可以设置线程的优先级
private int priority;

//可以配置线程是否为守护线程,默认为false
private boolean daemon = false;

//最终执行线程任务的`Runnable`
private Runnable target;

//描述线程组的类
private ThreadGroup group;

//此线程的上下文ClassLoader
private ClassLoader contextClassLoader;

//所有初始化线程的数目,用于自动编号匿名线程,当没有指定线程名称时,会自动为其编号
private static int threadInitNumber;

//此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。, 虚拟机可以用这个数字做任何喜欢的事情。, 一些虚拟机会忽略它。
private long stackSize;

//线程id
private long tid;

//用于生成线程ID
private static long threadSeqNumber;

//线程状态
private volatile int threadStatus = 0;

//线程可以拥有的最低优先级
public final static int MIN_PRIORITY = 1;

//分配给线程的默认优先级。
public final static int NORM_PRIORITY = 5;

//线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10;

Все имена атрибутов очень семантичны. На самом деле, вы можете догадаться, что они делают, увидев название. Это не так уж сложно~~

Конструктор потоков

Разобравшись со свойствами, посмотрите наThreadКак строятся экземпляры? Сначала просмотрите, сколько у него методов строительства:

Глядя на внутренний исходный код каждого метода построения, обнаруживается, что имя называетсяinitчастный метод, см. еще разinitМетод имеет две перегрузки, и его основной метод выглядит следующим образом:

   /**
     * Initializes a Thread.
     *
     * @param g                   线程组
     * @param target              最终执行任务的 `run()` 方法的对象
     * @param name                新线程的名称
     * @param stackSize           新线程所需的堆栈大小,或者 0 表示要忽略此参数
     * @param acc                 要继承的AccessControlContext,如果为null,则为 AccessController.getContext()
     * @param inheritThreadLocals 如果为 true,从构造线程继承可继承的线程局部的初始值
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        //线程名称为空,直接抛出空指针异常
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        //初始化当前线程对象的线程名称
        this.name = name;
        //获取当前正在执行的线程为父线程
        Thread parent = currentThread();
        //获取系统安全管理器
        SecurityManager security = System.getSecurityManager();
        //如果线程组为空
        if (g == null) {
            //如果安全管理器不为空
            if (security != null) {
                //获取SecurityManager中的线程组
                g = security.getThreadGroup();
            }
            //如果获取的线程组还是为空
            if (g == null) {
                //则使用父线程的线程组
                g = parent.getThreadGroup();
            }
        }
        
        //检查安全权限
        g.checkAccess();

        //使用安全管理器检查是否有权限
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        
        //线程组中标记未启动的线程数+1,这里方法是同步的,防止出现线程安全问题
        g.addUnstarted();
        
        //初始化当前线程对象的线程组
        this.group = g;
        //初始化当前线程对象的是否守护线程属性,注意到这里初始化时跟父线程一致
        this.daemon = parent.isDaemon();
        //初始化当前线程对象的线程优先级属性,注意到这里初始化时跟父线程一致
        this.priority = parent.getPriority();
        //这里初始化类加载器
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        //初始化当前线程对象的最终执行任务对象
        this.target = target;
        //这里再对线程的优先级字段进行处理
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        //初始化当前线程对象的堆栈大小
        this.stackSize = stackSize;

        //初始化当前线程对象的线程ID,该方法是同步的,内部实际上是threadSeqNumber++
        tid = nextThreadID();
    }

еще одна перегрузкаinitПриватный метод выглядит следующим образом, на самом деле внутренний вызов вышеinitметод:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

Затем посмотрите на все конструкторы:

  1. пустой конструктор

     public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }
    

    Внутренний вызовinitВторой перегруженный метод, параметры в основном являются значениями по умолчанию, имя потока жестко закодировано как"Thread-" + nextThreadNum()Формат,nextThreadNum()Для синхронизированного метода поддерживается внутреннее статическое свойство, указывающее количество потоков инициализации + 1:

     private static int threadInitNumber;
        private static synchronized int nextThreadNum() {
            return threadInitNumber++;
        }
    
  2. Пользовательские задачи выполненияRunnableконструктор объекта

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    

    Отличие от первого конструктора в том, что его можно настраиватьRunnableобъект

  3. Пользовательские задачи выполненияRunnableобъект иAccessControlContextконструктор объектов

     Thread(Runnable target, AccessControlContext acc) {
        init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    }
    
  4. пользовательская группа потоковThreadGroupи выполнять заданияRunnableконструктор объектов

    public Thread(ThreadGroup group, Runnable target) {
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }
    
  5. пользовательское имя потокаnameметод строительства

     public Thread(String name) {
        init(null, null, name, 0);
    }
    
  6. пользовательская группа потоковThreadGroupи название темыnameметод строительства

     public Thread(ThreadGroup group, String name) {
        init(group, null, name, 0);
    }
    
  7. Пользовательские задачи выполненияRunnableИмена объектов и потоковnameметод строительства

     public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    
  8. пользовательская группа потоковThreadGroupи название темыnameи выполнять заданияRunnableконструктор объектов

      public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
    }
    
  9. Все свойства являются пользовательскими конструкторами

      public Thread(ThreadGroup group, Runnable target, String name,
                  long stackSize) {
        init(group, target, name, stackSize);
    }
    

ThreadПредоставляет очень гибкий перегруженный метод построения, который разработчикам удобно настраивать под различные параметры.Threadобъект.

Общий метод

Вот некоторые из наиболее распространенных методов дляThreadЕсть некоторые локальные методы в , давайте оставим их в покое~

установить имя потока

Задайте имя потока.Этот метод является методом синхронизации.Во избежание проблем с безопасностью потока вы можете вызвать его вручную.ThreadМетод экземпляра задает имя также в конструктореThreadПри передаче имени потока в конструктор мы обычно задаем параметры при построении

   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);
          }
      }

получить имя потока

Внутренне напрямую возвращает свойство имени текущего объекта потока

  public final String getName() {
        return name;
    }

начать нить

public synchronized void start() {
        //如果不是刚创建的线程,抛出异常
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        //通知线程组,当前线程即将启动,线程组当前启动线程数+1,未启动线程数-1
        group.add(this);
        
        //启动标识
        boolean started = false;
        try {
            //直接调用本地方法启动线程
            start0();
            //设置启动标识为启动成功
            started = true;
        } finally {
            try {
                //如果启动呢失败
                if (!started) {
                    //线程组内部移除当前启动的线程数量-1,同时启动失败的线程数量+1
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

Наш обычный поток запуска должен вызыватьThreadизstart()метод, тоJavaВиртуальная машина позвонитThredизrunметод, вы можете увидетьThreadкласс также реализуетRunnableинтерфейс, переписанrunметод:

 @Override
    public void run() {
        //当前执行任务的Runnable对象不为空,则调用其run方法
        if (target != null) {
            target.run();
        }
    }

Threadдва способа использования:

  • наследоватьThreadкласс, переопределитьrunметод, то он непосредственно выполняется в это времяrunЛогика метода, не буду использоватьtarget.run();
  • выполнитьRunnableинтерфейс, переопределениеrunметод, потому чтоJavaОграничение единственного наследованияRunnableреализовать

Настроить поток демона

Важнейшей операцией является установкаdaemonАтрибуты

public final void setDaemon(boolean on) {
        //检查是否有安全权限
        checkAccess();
        //本地方法,测试此线程是否存活。, 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态
        if (isAlive()) {
            //如果线程先启动后再设置守护线程,将抛出异常
            throw new IllegalThreadStateException();
        }
        //设置当前守护线程属性
        daemon = on;
    }

Определить, является ли поток потоком демона

 public final boolean isDaemon() {
        //直接返回当前对象的守护线程属性
        return daemon;
    }

состояние потока

Сначала идет диаграмма состояний потока:

Получить статус потока:

 public State getState() {
        //由虚拟机实现,获取当前线程的状态
        return sun.misc.VM.toThreadState(threadStatus);
    }

Состояние потока в основном определяется классом внутреннего перечисления.Stateсочинение:

  public enum State {
      
        NEW,

      
        RUNNABLE,

      
        BLOCKED,

       
        WAITING,

       
        TIMED_WAITING,

       
        TERMINATED;
    }
  • NEW: только что созданный и еще не запущенный поток находится в этом состоянии.
  • RUNNABLE: поток, выполняющийся на виртуальной машине Java, находится в этом состоянии.
  • BLOCKED: поток, заблокированный в ожидании блокировки монитора, находится в этом состоянии, например, поток, столкнувшийся сsynchronizedСинхронизированный блок, он войдет в это состояние, после чего поток приостановит выполнение до тех пор, пока не будет получена запрошенная блокировка.
  • ОЖИДАНИЕ: в этом состоянии находится поток, бесконечно ожидающий выполнения другим потоком определенного действия.
    • Поток, ожидающий через метод wait(), ожидает метод notify()
    • Потоки, ожидающие выполнения метода join(), будут ожидать завершения целевого потока.
  • TIMED_WAITING: Ожидание выполнения действия другим потоком до тех пор, пока поток с указанным временем ожидания не окажется в этом состоянии.
    • Через метод wait() с тайм-аутом ожидающий поток ожидает метод notify()
    • Через метод join() переносится период ожидания, и ожидающий поток будет ждать завершения целевого потока.
  • TERMINATED: завершенный поток находится в этом состоянии, и в этот момент поток не может вернуться в состояние RUNNABLE.

нить сна

Это статический нативный метод, который приостанавливает текущий исполняемый поток и приостанавливает выполнение.millisмиллисекунды, выбрасываемые при прерывании снаInterruptedExceptionисключение прерывания

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

Проверить жив ли поток

Собственный метод, который проверяет, жив ли этот поток. Поток жив, если он был запущен и не умер.

    /**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */
    public final native boolean isAlive();

приоритет потока

  • Установить приоритет потока
    /**
     * Changes the priority of this thread.
     * <p>
     * First the <code>checkAccess</code> method of this thread is called
     * with no arguments. This may result in throwing a
     * <code>SecurityException</code>.
     * <p>
     * Otherwise, the priority of this thread is set to the smaller of
     * the specified <code>newPriority</code> and the maximum permitted
     * priority of the thread's thread group.
     *
     * @param newPriority priority to set this thread to
     * @exception  IllegalArgumentException  If the priority is not in the
     *               range <code>MIN_PRIORITY</code> to
     *               <code>MAX_PRIORITY</code>.
     * @exception  SecurityException  if the current thread cannot modify
     *               this thread.
     * @see        #getPriority
     * @see        #checkAccess()
     * @see        #getThreadGroup()
     * @see        #MAX_PRIORITY
     * @see        #MIN_PRIORITY
     * @see        ThreadGroup#getMaxPriority()
     */
    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);
        }
    }

прерывание потока

есть одинstop()Экземплярный метод может принудительно завершать поток, но этот метод был помечен как устаревший, потому что он слишком агрессивен, и программистам не рекомендуется его использовать, потому чтопринудительно завершить потокприведет к несогласованности данных.

Здесь есть три метода прерывания потока:

//实例方法,通知线程中断,设置标志位
 public void interrupt(){}
 //静态方法,检查当前线程的中断状态,同时会清除当前线程的中断标志位状态
 public static boolean interrupted(){}
 //实例方法,检查当前线程是否被中断,其实是检查中断标志位
 public boolean isInterrupted(){}

Анализ метода Interrupt()

/**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        //检查是否是自身调用
        if (this != Thread.currentThread())
            //检查安全权限,这可能导致抛出{@link * SecurityException}。
            checkAccess();
        
        //同步代码块
        synchronized (blockerLock) {
            Interruptible b = blocker;
            //检查是否是阻塞线程调用
            if (b != null) {
                //设置线程中断标志位
                interrupt0(); 
                //此时抛出异常,将中断标志位设置为false,此时我们正常会捕获该异常,重新设置中断标志位
                b.interrupt(this);
                return;
            }
        }
        //如无意外,则正常设置中断标志位
        interrupt0();
    }
  • Метод прерывания потока не приводит к немедленному завершению потока, а отправляет уведомление в поток, чтобы сообщить целевому потоку, что кто-то хочет, чтобы вы вышли~
  • Может вызываться только сам по себе, иначе может выкинутьSecurityException
  • Вызов метода прерывания определяется самим целевым потоком, прерывать ли его, и вызывается ли он одновременноwait,join,sleepи другие методы, текущий поток перейдет в состояние блокировки, что может произойти в это время.InterruptedExceptionаномальный
  • Неразумно, чтобы заблокированный поток снова вызывал метод прерывания.
  • Прерывание неактивного потока не имеет никакого эффекта

Проверьте, не прерван ли поток:

    /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     
     测试此线程是否已被中断。, 线程的<i>中断*状态</ i>不受此方法的影响。
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if this thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see     #interrupted()
     * @revised 6.0
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

Статический метод очистит бит флага прерывания текущего потока:

   /**
     *测试当前线程是否已被中断。, 此方法清除线程的* <i>中断状态</ i>。, 换句话说,如果要连续两次调用此方法,则* second调用将返回false(除非当前线程再次被中断,在第一次调用已清除其中断的*状态   之后且在第二次调用已检查之前), 它)
     *
     * <p>A thread interruption ignored because a thread was not alive
     * at the time of the interrupt will be reflected by this method
     * returning false.
     *
     * @return  <code>true</code> if the current thread has been interrupted;
     *          <code>false</code> otherwise.
     * @see #isInterrupted()
     * @revised 6.0
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

Суммировать

запишите, как вы читаетеThreadНекоторые мысли по исходному коду класса, но я могу только держаться подальше от многих локальных методов, используемых в нем, и есть еще некоторые коды, которые я не понимаю. Позже на практикеThreadДелайте больше сводных записей.

наконец

Из-за длины статьи давайте пока запишем их. Оригинальные статьи будут время от времени обновляться в будущем. Добро пожаловать в публичный аккаунт «Чжан Шаолинь»!