Thread线程源码详解(2)

x33g5p2x  于2021-08-23 转载在 Java  
字(15.9k)|赞(0)|评价(0)|浏览(239)

前言

Thread务实基础最佳文章,类目如下:

在这里插入图片描述

一 线程的命名规则

1 线程的默认命名规则

 @Test
    public void testThreadName(){
        // Thread-3
        Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
        thread.start();
    }

可以看见上面代码输出了当前线程的名称Thread-3,我们猜测线程默认的规则是(Thread-int)。然后我们点入构造器源码(如下)发现确实如此。

/**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, target, gname)}, where {@code gname} is a newly generated
     * name. Automatically generated names are of the form
     * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
     *
     * @param  target
     *         the object whose {@code run} method is invoked when this thread
     *         is started. If {@code null}, this classes {@code run} method does
     *         nothing.
     */
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

2 修改线程的默认命名规则

我们实际开发中不可能使用默认的线程命名规则,这么很难分清楚到底是哪个线程,在出现线程安全问题排查的时候也是很麻烦,为此我们使用一个带有线程名称的Thread构造器。如下代码指定了一个线程名称叫做youku1327的线程

 @Test
    public void testThreadAllocateName(){
        // youku1327
        Thread thread = new Thread(() -> System.out.println((Thread.currentThread().getName())),"youku1327");
        thread.start();
    }

然后我们可以看下它的构造器:

    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

二 线程组

1默认的线程组

我们启动一个线程发现它默认是属于main线程组的代码如下:

@Test
    public void testThreadGroup(){
        // java.lang.ThreadGroup[name=main,maxpri=10]
        Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getThreadGroup()));
        thread.start();
    }

我们深入源码看下为什么默认的线程归属于main线程组

 // 进入构造器
 public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
// 进入Init方法 这里截取部分
  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) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
    

我们 发现 当 g 为空时候就会调用父线程的线程组作为当前线程的线程组。那么父线程(Thread parent = currentThread();)就是执行当前线程启动的线程(源码如下),在java1.7之后的规范中线程的启动入口是main函数,当我们不指定线程的线程组时,main线程组默认是所有线程的父线程

   /**
     * Returns a reference to the currently executing thread object.
     *
     * @return  the currently executing thread.
     */
    public static native Thread currentThread();

2 指定线程组

指定线程组很简单,我们创建一个带名称的ThreadGroup对象然后将其作为参数传给Thread构造器,代码清单如下:

 @Test
    public void testAllocateThreadGroup(){
        ThreadGroup threadGroup = new ThreadGroup("youku1327");
        // java.lang.ThreadGroup[name=youku1327,maxpri=10]
        Thread thread = new Thread(threadGroup,() -> System.out.println(Thread.currentThread().getThreadGroup()));
        thread.start();
    }

三 认识守护线程

1 守护线程介绍

守护线程是只运行在系统后台的线程,在我们jvm中最著名的守护线程就是垃圾回收的守护线程,守护线程随着main函数的启动而启动,随着main函数的结束而终止;如果没有设置为守护线程,那么随着main函数的结束,我们创建的线程还会继续运行,就像switch函数中 语句后面没带break具有击穿效果类似(代码如下)这段程序会一直运行,即使main函数已经终止:

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                // do nothing
            }
        });
        thread.start();
    }

设置守护线程只要调用 setDeamon(boolean on)方法即可,代码如下:

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                // do nothing
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

2 守护线程源码解析

我们继续点击serDeamon()方法(源码如下)发现只要这个线程是活的再去设置当前线程为守护线程就会抛出IllegalThreadStateException异常信息,换句话说就是设置守护线程必须在执行start()方法之前,守护线程也可以称为用户线程,当所有的线程都是守护线程的时候,jvm就会退出;并且被设置为守护线程之后不可对其进行修改

/**
     * Marks this thread as either a {@linkplain #isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
     * <p> This method must be invoked before the thread is started.
     *
     * @param  on
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain #isAlive alive}
     *
     * @throws  SecurityException
     *          if {@link #checkAccess} determines that the current
     *          thread cannot modify this thread
     */
    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

四 Thread常用API介绍

1 sleep

先看一下官方文档介绍:

static void	sleep(long millis)

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.

static void	sleep(long millis, int nanos)

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers.

static void sleep(long millis) 方法是休眠指定的毫秒数,当然是以系统的精确计算为准。
static void sleep(long millis, int nanos) 方法是休眠指定的毫秒数和纳秒数。
细心的同学都发现了这两个方法都是静态方法,我们可以直接调用,代码清单如下:

public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    System.out.println("youku1327");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    }

当执行这段代码时你会感觉到明显的打印停顿,也就是线程休眠了2秒。我们深入源码分析一下sleep方法:

    /**
     * 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;

从源码的介绍可以看出这是个native方法,由JIN所调用,并且除了休眠之外,sleep方法不会释放任何monitors的拥有权;此外,如果当前线程被中断,会抛出InterruptedException中断异常,中断的状态也会随之清除

2 yield

我们看下官方源码:

/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();

yield方法字面意思是屈服的意思,暗示调度器当前线程放弃了对进程的控制权,调度器是可以随意忽略此暗示。yield方法并非是一个常用的方法,旨在相对的线程中提高性能,有可能会造成cup过度使用,一般使用在debug或者测试中,也使用在设计并发控制构造器中比如locks。

3 priority

public final int getPriority()
public final void setPriority(int newPriority)

如下代码我们分别给两个线程设置不同的优先级,先打印出youku2然后才是youku1说明现场确实是有优先级的,设置优先级的越高,线程被调度器选中执行的概率越高

    @Test
    public void testPriority(){
        Thread thread1 = new Thread(() -> System.out.println("youku1"));
        thread1.setPriority(2);
        Thread thread2 = new Thread(() -> System.out.println("youku2"));
        thread2.setPriority(10);
        thread1.start();
        thread2.start();
    }

我们看下源码(源码如下)可以看见线程的最小认优先级是1级,最高的优先级是10级,当我们设置的优先级不在1-10范围内,会抛出IllegalArgumentException异常,当存在线程组并且我们设置的优先级大于线程组的优先级则会使用线程组的优先级.

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

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

验证一下我们设置的线程优先级高于线程组的优先级时使用线程组的优先级发现确实如此,代码如下:

    @Test
    public void testPriority1(){
        ThreadGroup threadGroup = new ThreadGroup("youku1327");
        threadGroup.setMaxPriority(6);
        Thread thread = new Thread(threadGroup,() -> System.out.println("hello priority"));
        thread.setPriority(7);
        thread.start();
        // java.lang.ThreadGroup[name=youku1327,maxpri=6]
        System.out.println(thread.getThreadGroup());
        // 6
        System.out.println(thread.getPriority());
    }

我们已经知道如何设置线程的优先级,现在我们看看下面代码获得线程默认的优先级发现是5.

    @Test
    public void testgGetPriority1(){
        Thread thread = new Thread(() -> System.out.println("hello priority"));
        // 5
        System.out.println(thread.getPriority());
    }

我们深入源码看下发现默认的优先级确实是5

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    public final int getPriority() {
        return priority;
    }

4 getId

如下代码发现打印出来的线程id不是0,是不是有点奇怪,没错java程序除了我们写代码运行的线程之外还有之前说过的守护线程,这些线程是优于我们编写的线程启动,所以我们的线程id是分配在这些线程之后当然不可能是从0开始

    @Test
    public void getPid(){
        Thread thread = new Thread(() -> System.out.println("hello priority"));
        thread.start();
        System.out.println(thread.getId());//16
    }

通过源码发现(源码如下)可知线程的id唯一标识是long类型的,当线程被创建的时候生成,在线程存活期间线程的id是无法改变的,当线程终止时才重新被使用

/**
     * Returns the identifier of this Thread.  The thread ID is a positive
     * <tt>long</tt> number generated when this thread was created.
     * The thread ID is unique and remains unchanged during its lifetime.
     * When a thread is terminated, this thread ID may be reused.
     *
     * @return this thread's ID.
     * @since 1.5
     */
    public long getId() {
        return tid;
    }

线程的id生成的规则是每次在之前的线程id上加1 ,源码如下

    /* For generating thread ID */
    private static long threadSeqNumber;
    
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }

5 currentThread

获取当前线程。

    @Test
    public void getCurrentThread(){
        // main
        System.out.println(Thread.currentThread().getName());
    }

6 getState

线程的转态其实就是线程生命周期里面的一部分在上一篇的多线程文章中已经讲过,想要详细了解的同学可以看我上一篇文章

    @Test
    public void getState(){
        Thread thread = new Thread(() -> System.out.println("youku1327"));
        thread.start();
        // RUNNABLE
        System.out.println(thread.getState());
    }

源码中的线程状态6种,不赘述。

   public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }
    
    public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

7 isActive

判断线程是否存活的,依据条件是线程已经启动并且未死亡判定为存活,否则判定为死亡,示例如下:

    @Test
    public void getisAlive(){
        Thread thread = new Thread(() -> System.out.println("youku1327"));
        thread.start();
        // true
        System.out.println(thread.isAlive());
    }

8 contextClassLoader

默认的线程加载器其实是父类的上下文类加载器,我们启动一个线程默认的父类加载器是Main,其加载器是applicationClassLoader,有兴趣了解类的加载机制,也可以看我jvm分栏文章。

    @Test
    public void testContextClassLoader(){
        Thread thread = new Thread(() -> System.out.println("youku1327"));
        thread.start();
        // sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(thread.getContextClassLoader());
    }

源码片段如下:

        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;

设置上下文类加载器的方法:
void setContextClassLoader(ClassLoader cl)

    @Test
    public void testSetContextClassLoader(){
        Thread thread = new Thread(() -> System.out.println("youku1327"));
        ClassLoader classLoader = new ClassLoader() {
            @Override
            public URL getResource(String name) {
                return super.getResource(name);
            }
        };
        thread.setContextClassLoader(classLoader);
        thread.start();
        // com.youku1327.base.ThreadTest$1@3c7cfcbb
        System.out.println(thread.getContextClassLoader());
    }

9 interrupt

public void interrupt()

中断方法就是打断线程的阻塞状态。如下的方法会造成线程阻塞当我们调用interrupt()方法就会打断阻塞状态,并且抛出InterruptedException,清除标志位
object 类中的方法
wait()
wait(long)
wait(long, int)
thread类中的方法:
join()
join(long)
join(long, int)
sleep(long)
sleep(long, int)
示例代码如下:

   public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    TimeUnit.MILLISECONDS.sleep(4);
                } catch (InterruptedException e) {
                    // 抛出异常会清除中断标志
                    System.out.println("clean the interrupt flag");
                    //e.printStackTrace();
                }
            }
        }, "youku1327");
        // 启动线程
        thread.start();
        //  确保线程已经启动
        TimeUnit.MILLISECONDS.sleep(1);
        thread.interrupt();
        // 确保线程已经执行中断
        TimeUnit.MILLISECONDS.sleep(1);
        // 因为标志位已经被清楚 thread is interrupt :false
        System.out.println("thread is interrupt :"+thread.isInterrupted());

    }

如果是IO阻塞(java.nio.channels.InterruptibleChannel),IO通道将会被关闭,并且会设置中断状态,抛出ClosedByInterruptException异常。

如果是Selector阻塞(java.nio.channels.Selector),中断标志位会被设置,立即从选择器操作中返回(返回值肯能非零,如调用了wakeup方法一样返回了正在操作中的选择器);

10 isInterrupted

public boolean isInterrupted()

isInterrupted用于判断中断标识是否被中断,是返回true,否则返回false。示例如下:

 public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true){
                // do nothing
            }
        },"youku1327");
        thread.setDaemon(true);
        thread.start();
        TimeUnit.MILLISECONDS.sleep(2);
        // the thread is interrupt :false
        System.out.println("the thread is interrupt :"+thread.isInterrupted());
        thread.interrupt();
        // the thread is interrupt :true
        System.out.println("the thread is interrupt :"+thread.isInterrupted());
    }

interrupted 方法不会对中断标识造成任何影响,原因是其不会清除中断标识,源码如下:

   /**
     * Tests whether this thread has been interrupted.  The <i>interrupted
     * status</i> of the thread is unaffected by this method.
     *
     * <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);
    }
    /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

11 interrupted

public static boolean interrupted()

调用中断后,如果2次都成功调用此方法, 判定第一次会返回true ,并且会清除中断标志,后面都是false。示例如下

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true){
                System.out.println("the thread is interrupt :"+Thread.interrupted());
            }
        },"youku1327");
        // 设置为守护线程
        thread.setDaemon(true);
        thread.start();
        TimeUnit.MILLISECONDS.sleep(1);
        thread.interrupt();
    }

源码如下

/**
     * Tests whether the current thread has been interrupted.  The
     * <i>interrupted status</i> of the thread is cleared by this method.  In
     * other words, if this method were to be called twice in succession, the
     * second call would return false (unless the current thread were
     * interrupted again, after the first call had cleared its interrupted
     * status and before the second call had examined it).
     *
     * <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);
    }

12 join

  • public final void join()
  • public final void join(long millis)
  • public final void join(long millis, int nanos)

thread.join()的含义是:当线程A执行了thread.join方法,那么线程A会等待线程B终止之后才从join()方法中返回,如过设置超时时间为0则会发生永远等待,如果发生中断,那么会抛出InterruptedException异常,并且清除中断标志。示例代码如下:

@Test
    public void testJoin(){
        Thread thread = new Thread(() -> System.out.println("youku1327"));
        thread.start();
        try {
            thread.join(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.isAlive());//false
        System.out.println(thread.getState());//TERMINATED
    }

源码如下:

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait 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 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;
            }
        }
    }

如果已经理解join方法主线程执行以下代码会一直处于等待状态。

    @Test
    public void testJoin(){
        Thread thread = new Thread(() -> {
            while (true){
                // do nothing
            }
        });
        thread.setDaemon(true);
        thread.start();
        try {
            thread.join(0);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.isAlive());//false
        System.out.println(thread.getState());//TERMINATED
    }

相关文章