资讯专栏INFORMATION COLUMN

从源码的角度再学「Thread」

abson / 503人阅读

摘要:前言中的线程是使用类实现的,在初学的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习,愿此后对的实践更加得心应手。如果一个线程已经启动并且尚未死亡,则该线程处于活动状态。

前言

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

Thread 常用属性

阅读一个Java类,先从它拥有哪些属性入手:

//线程名称,创建线程时可以指定线程的名称
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 构造方法

了解了属性之后,看看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);
    }

接下来看看所有构造方法:

空构造方法

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

内部调用的是init第二个重载方法,参数基本都是默认值,线程名称写死为"Thread-" + nextThreadNum()格式,nextThreadNum()为一个同步方法,内部维护一个静态属性表示线程的初始化数量+1:

 private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

自定义执行任务Runnable对象的构造方法

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

与第一个构造方法区别在于可以自定义Runnable对象

自定义执行任务Runnable对象和AccessControlContext对象的构造方法

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

自定义线程组ThreadGroup和执行任务Runnable对象的构造方法

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

自定义线程名称name的构造方法

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

自定义线程组ThreadGroup和线程名称name的构造方法

 public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

自定义执行任务Runnable对象和线程名称name的构造方法

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

自定义线程组ThreadGroup和线程名称name和执行任务Runnable对象的构造方法

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

全部属性都是自定义的构造方法

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

我们正常的启动线程都是调用Threadstart()方法,然后Java虚拟机内部会去调用Thredrun方法,可以看到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同步块,就会进入此状态,此时线程暂停执行,直到获得请求的锁

WAITING:无限期等待另一个线程执行特定操作的线程处于此状态

通过 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
     *          interrupted status 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  true if this thread is alive;
     *          false otherwise.
     */
    public final native boolean isAlive();
线程优先级

设置线程优先级

    /**
     * Changes the priority of this thread.
     * 

* First the checkAccess method of this thread is called * with no arguments. This may result in throwing a * SecurityException. *

* Otherwise, the priority of this thread is set to the smaller of * the specified newPriority 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 MIN_PRIORITY to * MAX_PRIORITY. * @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.
     *
     * 

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

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

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

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

If none of the previous conditions hold then this thread"s interrupt * status will be set.

* *

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 interrupted
     * status of the thread is unaffected by this method.
     
     测试此线程是否已被中断。, 线程的中断*状态不受此方法的影响。
     *
     * 

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 true if this thread has been interrupted; * false otherwise. * @see #interrupted() * @revised 6.0 */ public boolean isInterrupted() { return isInterrupted(false); }

静态方法,会清空当前线程的中断标志位:

   /**
     *测试当前线程是否已被中断。, 此方法清除线程的* 中断状态。, 换句话说,如果要连续两次调用此方法,则* second调用将返回false(除非当前线程再次被中断,在第一次调用已清除其中断的*状态   之后且在第二次调用已检查之前), 它)
     *
     * 

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 true if the current thread has been interrupted; * false otherwise. * @see #isInterrupted() * @revised 6.0 */ public static boolean interrupted() { return currentThread().isInterrupted(true); }

总结

记录自己阅读Thread类源码的一些思考,不过对于其中用到的很多本地方法只能望而却步,还有一些代码没有看明白,暂且先这样吧,如果有不足之处,请留言告知我,谢谢!后续会在实践中对Thread做出更多总结记录。

最后

由于篇幅较长,暂且先记录这些吧,后续会不定期更新原创文章,欢迎关注公众号 「张少林同学」!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73112.html

相关文章

  • JAVA 中 CAS

    摘要:我们继续看代码的意思是这个是一段内嵌汇编代码。也就是在语言中使用汇编代码。就是汇编版的比较并交换。就是保证在多线程情况下,不阻塞线程的填充和消费。微观上看汇编的是实现操作系统级别的原子操作的基石。 原文地址:https://www.xilidou.com/2018/02/01/java-cas/ CAS 是现代操作系统,解决并发问题的一个重要手段,最近在看 eureka 的源码的时候。...

    CocoaChina 评论0 收藏0
  • vue创建到完整饿了么(2)路由

    摘要:首先在下创建文件夹,在下创建文件,所有的路由控制都写在这个文件里。表示路径,表示显示的页面要显示哪个文件,表示的嵌套的路由。 说明 上一篇地址--创建 苍渡大神Github项目源码地址--源码地址 下一篇地址--引入UI框架 home.vue 创建 根据源码,先在src文件夹下新建文件夹page,在page中新建home文件夹,在home文件夹中新建home.vue,hom...

    mmy123456 评论0 收藏0
  • Thread源码解读(3)——线程中断interrupt

    摘要:现在终止一个线程,基本上只能靠曲线救国式的中断来实现。中断机制的核心在于中断状态和异常中断状态设置一个中断状态清除一个中断状态方法同时会返回线程原来的中断的状态。中断异常中断异常一般是线程被中断后,在一些类型的方法如中抛出。 前言 系列文章目录 线程中断是一个很重要的概念,通常,取消一个任务的执行,最好的,同时也是最合理的方法,就是通过中断。 本篇我们主要还是通过源码分析来看看中断的概...

    fevin 评论0 收藏0
  • Java面试题必备知识之ThreadLocal

    摘要:方法,删除当前线程绑定的这个副本数字,这个值是的值,普通的是使用链表来处理冲突的,但是是使用线性探测法来处理冲突的,就是每次增加的步长,根据参考资料所说,选择这个数字是为了让冲突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列举下关于ThreadLocal常见的疑问,希望可以通过这篇学...

    Maxiye 评论0 收藏0

发表评论

0条评论

abson

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<