多线程学习(三)---Thread源码解读

参考文章:

Thread源码剖析
多线程全面详解总结
Java多线程之interrupt()的深度研究

jdk版本:1.8.0_201

1. 线程名

我们在使用多线程的时候,想要查看线程名是很简单的,调用Thread.currentThread().getName()即可。

如果没有做什么的设置,我们会发现线程的名字是这样子的:主线程叫做main,其他线程是Thread-x.

下面来看它的实现方法:

/**
     * Allocates a new {@code Thread} object. This constructor has the same
     * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
     * {@code (null, null, 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.
     */
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

nextThreadNum()方法实现:

/* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

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类还提供了设置线程名的方法:

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

检查是否有权限的方法checkAccess()

public final void checkAccess() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkAccess(this);
        }
    }

测试一下:

public class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        Runnable thread = new MyThread();
        Thread thread1 = new Thread(thread, "set name1");
        Thread thread2 = new Thread(thread, "set name2");
        thread2.setName("set name3");
        
        thread1.start();
        thread2.start();
        
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果:

main
set name3
set name1

2. 守护线程

守护线程是为其他线程服务的

  • 垃圾回收线程就是守护线程

守护线程有一个特点:

  • 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
  • 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行了

使用线程的时候要注意的地方

  1. 线程启动前设置为守护线程,方法是setDaemon(boolean on)
  2. 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
  3. 守护线程中产生的新线程也是守护线程

setDaemon(boolean on)方法如下:

public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

可以看到,如果线程已经启动,则会抛出异常。

测试一下:

public class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        Runnable thread = new MyThread();
        Thread thread1 = new Thread(thread, "set name1");
        Thread thread2 = new Thread(thread, "set name2");
        thread2.setName("set name3");
        
        thread2.setDaemon(true);
        
        thread1.start();
        thread2.start();
        
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果:

set name1
set name3
main

3. 优先级线程

线程优先级高仅仅表示线程获取的CPU时间片的几率高,但这不是一个确定的因素。

线程的优先级是高度依赖于操作系统的,Windows和Linux就有所区别(Linux下优先级可能就被忽略了)

可以看到的是,Java提供的优先级默认是5,最低是1,最高是10:

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

设置优先级的方法:

 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);
        }
    }
private native void setPriority0(int newPriority);

4. 线程生命周期

4.1 线程睡眠—sleep

线程睡眠的方法:

  1. sleep(long millis) 在指定的毫秒数内让正在执行的线程休眠。
  2. sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。
public static native void sleep(long millis) throws InterruptedException;

public static void sleep(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++;
        }

        sleep(millis);
    }

调用sleep方法会进入阻塞状态,等时间到了,进入的是就绪状态而并非是运行状态。
测试一下:

public class MyThread implements Runnable {

    private int time = 5;
    
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:S");

    @Override
    public void run() {
        while (time >= 0) {
            System.out.println(Thread.currentThread().getName() + ":" + time-- + " " + sdf.format(new Date()));
            try {
                // 睡眠1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Runnable thread = new MyThread();
        Thread thread1 = new Thread(thread, "倒计时1");
        Thread thread2 = new Thread(thread, "倒计时2");

        thread1.start();
        thread2.start();
    }
}

运行结果:

倒计时1:5 09:17:59:87
倒计时2:4 09:17:59:87
倒计时2:3 09:18:00:88
倒计时1:2 09:18:00:88
倒计时1:1 09:18:01:88
倒计时2:0 09:18:01:89

注意:
Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒,就像本例。

4.2 线程让步—yield

该方法和sleep方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用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方法会先让别的线程执行,但是不确保真正让出CPU。

测试一下:

public class MyThread implements Runnable {

    private int time = 10;

    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:S");

    @Override
    public void run() {
        while (time >= 0) {
            System.out.println(Thread.currentThread().getName() + ":" + time-- + " " + sdf.format(new Date()));
            if (time % 2 == 0) {
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) {
        Runnable thread = new MyThread();
        Thread thread1 = new Thread(thread, "倒计时1");
        Thread thread2 = new Thread(thread, "倒计时2");

        thread1.start();
        thread2.start();
    }
}

运行结果:

倒计时1:10 09:38:43:105
倒计时2:9 09:38:43:105
倒计时1:8 09:38:43:107
倒计时2:7 09:38:43:107
倒计时1:6 09:38:43:107
倒计时1:5 09:38:43:107
倒计时1:3 09:38:43:107
倒计时2:4 09:38:43:107
倒计时1:2 09:38:43:107
倒计时2:1 09:38:43:108
倒计时1:0 09:38:43:108

sleep和yield的区别:
①、sleep方法声明抛出InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
②、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态。

4.3 线程合并—join

调用join方法,会等待该线程执行完毕后才执行别的线程。
查看源码:

/**
     * Waits for this thread to die.
     *
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @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 void join() throws InterruptedException {
        join(0);
    }

第一句话就说明了,需要等待这个线程死亡,也就是调用完run()方法。
继续看源码:

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

从注释可以看出,0代表永远等待。
循环调用wait方法,当线程终止了,会调用notifyAll方法来唤醒。

    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 native void wait(long timeout) throws InterruptedException;

测试一下:

public class MyThread implements Runnable {

    private int time = 10;

    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss:S");

    @Override
    public void run() {
        while (time >= 0) {
            System.out.println(Thread.currentThread().getName() + ":" + time-- + " " + sdf.format(new Date()));
        }
        if (time < 0) {
            System.out.println(Thread.currentThread().getName() + ": end");
        }
    }

    public static void main(String[] args) {
        Runnable thread = new MyThread();
        Thread thread1 = new Thread(thread, "倒计时1");
        Thread thread2 = new Thread(thread, "倒计时2");

        thread1.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

运行结果:

倒计时1:10 09:44:23:31
倒计时1:9 09:44:23:31
倒计时1:8 09:44:23:32
倒计时1:7 09:44:23:32
倒计时1:6 09:44:23:32
倒计时1:5 09:44:23:32
倒计时1:4 09:44:23:32
倒计时1:3 09:44:23:32
倒计时1:2 09:44:23:32
倒计时1:1 09:44:23:32
倒计时1:0 09:44:23:32
倒计时1: end
倒计时2: end

4.4 终止线程—interrupt

线程中断在之前的版本有stop方法,但是被设置过时了。现在已经没有强制线程终止的方法了。

由于stop方法可以让一个线程A终止掉另一个线程B

  • 被终止的线程B会立即释放锁,这可能会让对象处于不一致的状态。
  • 线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法将线程B终止,那就很无辜了。

总而言之,Stop方法太暴力了,不安全,所以被设置过时了。

我们一般使用的是interrupt来请求终止线程。

  • 要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了(明白这一点非常重要)
  • 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
  • 具体到底中断还是继续运行,应该由被通知的线程自己处理
Thread t1 = new Thread( new Runnable(){
    public void run(){
        // 若未发生中断,就正常执行任务
        while(!Thread.currentThread.isInterrupted()){
            // 正常任务代码……
        }
        // 中断的处理代码……
        doSomething();
    }
} ).start();

再次说明:调用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.
     *

前面说了,java设计者设置中断标志的目的是想由被通知的线程自己处理,而这些方式都阻塞掉了。
被阻塞掉的线程调用中断方法是不合理的(不允许中断已阻塞的线程),因为可能会造成中断无效。

     * <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())
            checkAccess();

        synchronized (blockerLock) {
            / 看看是不是阻塞的线程调用,如sleep、wait、join
            Interruptible b = blocker;
            / 如果是,抛出异常,将中断标志位改为false
            / 一般我们会在catch中再次修改
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        / 如果很顺利,就可以直接修改标志位
        interrupt0();
    }

现在看下抛出的异常InterruptedException

/**
 * Thrown when a thread is waiting, sleeping, or otherwise occupied,
 * and the thread is interrupted, either before or during the activity.
 * Occasionally a method may wish to test whether the current
 * thread has been interrupted, and if so, to immediately throw
 * this exception.  The following code can be used to achieve
 * this effect:
 * <pre>
 *  if (Thread.interrupted())  // Clears interrupted status!
 *      throw new InterruptedException();
 * </pre>
 *
 * @author  Frank Yellin
 * @see     java.lang.Object#wait()
 * @see     java.lang.Object#wait(long)
 * @see     java.lang.Object#wait(long, int)
 * @see     java.lang.Thread#sleep(long)
 * @see     java.lang.Thread#interrupt()
 * @see     java.lang.Thread#interrupted()
 * @since   JDK1.0
 */

当线程正在执行sleep、wait等方法时,调用线程中断,就会抛出这个异常

public
class InterruptedException extends Exception {
    private static final long serialVersionUID = 6700697376100628473L;

    /**
     * Constructs an <code>InterruptedException</code> with no detail  message.
     */
    public InterruptedException() {
        super();
    }

    /**
     * Constructs an <code>InterruptedException</code> with the
     * specified detail message.
     *
     * @param   s   the detail message.
     */
    public InterruptedException(String s) {
        super(s);
    }
}

所以说:interrupt方法压根是不会对线程的状态造成影响的,它仅仅设置一个标志位罢了

interrupt线程中断还有另外两个方法(检查该线程是否被中断):

  • 静态方法interrupted()–>会清除中断标志位
  • 实例方法isInterrupted()–>不会清除中断标志位
/**
     * 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);
    }

通过注释可以看出,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);
    }

通过注释可以看出,isInterrupted()方法不会影响中断标志位

下面测试一下:

public class MyThread implements Runnable {

    @Override
    public void run() {
        int i = 0;
        try {
            while (i < 100) {

                // 睡个半秒钟我们再执行
                Thread.sleep(500);

                System.out.println(i++);
            }
        } catch (InterruptedException e) {

            // 判断该阻塞线程是否还在
            System.out.println("该阻塞线程是否还在:" + Thread.currentThread().isAlive());

            // 判断该线程的中断标志位状态
            System.out.println("该线程的中断标志位:" + Thread.currentThread().isInterrupted());

            System.out.println("In Runnable");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start();

        try {
            // 睡眠2秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            System.out.println("In main");
            e.printStackTrace();
        }
        
        thread.interrupt();
    }
}

运行结果如下:

0
1
2
3
该阻塞线程是否还在:true
该线程的中断标志位:false
In Runnable
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at testThread.MyThread.run(MyThread.java:12)
	at java.lang.Thread.run(Thread.java:748)

执行流程分析:

  1. main方法所在的线程睡眠了2秒,此时自定义的线程在其他线程中执行
  2. 自定义线程中,每隔半秒,执行一次操作
  3. main方法所在线程在睡眠2秒后,设置了自定义线程的中断
  4. 由于自定义线程中执行了sleep方法,因此该线程会立马中断,停止阻塞,并抛出InterruptedException异常。在catch中,可以看出这个线程还是存活状态,但是该线程明明已经被中断,而isInterrupted()方法竟然返回了false,为什么呢?

请往上看interrupt()方法的源码,我们调用sleep、wait等此类可中断(throw InterruptedException)方法时,一旦方法抛出InterruptedException,当前调用该方法的线程的中断状态就会被jvm自动清除了,就是说我们调用该线程的isInterrupted方法时是返回false。如果你想保持中断状态,可以再次调用interrupt方法设置中断状态。这样做的原因是,java的中断并不是真正的中断线程,而只设置标志位(中断位)来通知用户。如果你捕获到中断异常,说明当前线程已经被中断,不需要继续保持中断位。
interrupted是静态方法,返回的是当前线程的中断状态。例如,如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。

猜你喜欢

转载自blog.csdn.net/a770794164/article/details/91994039
今日推荐