尽力去探究Thread的方方面面

前言

接下来的这一段时间都将探索跟线程有关的内容,包括各种锁,对Thread的总结如下:

线程具有优先级,高优先级的线程优先于低优先级的线程执行,当在某个线程中创建新线程时,新线程的优先级被设置成当前线程的优先级;JVM启动时,默认有一个非守护线程(调用某个类的main方法);线程能被标记为守护线程,每个线程都可以指定名称,未指定的情况下将由底层帮其生成一个新名称;有两种方式来创建线程,如下所示:


    // 在只有一个实例的情况下只能创建一个线程
    class TestA extends Thread {

        @Override
        public void run() {
            System.out.println("TestA");
        }
    }

    // 在只有一个实例的情况下可创建多个线程,这种方式更为常见
    class TestB implements Runnable {

        @Override
        public void run() {
            System.out.println("TestB");
        }
    }

方方面面

探索Thread源码是基于JDK1.8,在探索之前先了解下线程的状态

  • 新建(NEW):新创建一个对象,但还没有开始调start方法。

  • 可运行(RUNNABLE):调用start方法后,该线程处于就绪状态,简单来就是还未运行,当线程调度器把时间片分配给该线程后变成了运行中状态。

  • 阻塞(BLOCKED):等待同步控制方法或代码块上的对象锁,因为该对象锁已经被其他线程所占用。当线程处于阻塞状态时,线程调度器将忽略该线程,不会分配任何CPU时间,直到该线程进入到就绪状态,它才有可能执行操作。

  • 等待(WAITING):等待另外一个线程执行特定的操作,如唤醒(通知)或终止,调用Object#wait、Thread#join、LockSupport#park后会处于该状态。

  • 超时等待(TIMED_WAITING):该状态与等待有些类似,只不过它是有限时间内的等待,也就是说,当另外一个线程未执行特定的操作时,经过指定的时间后该线程将会变成该状态(此时未获取到锁),当获取到锁之后就会变成可运行状态(这个状态的转变是个人猜想的,Java规范与注释都未明确说明),调用Thread#sleep(long)、Object#wait(long)、Thread#join(long)、LockSupport#parkUntil可能会处于该状态。顺便提一下,若方法中的参数都为0,那么就相当于直接调用了wait方法,此时的状态会是等待

  • 终止(TERMINATED):线程执行完成,指的是从run方法返回。

线程状态图

简要阐述下几个重要概念,该内容摘抄自Java规范。

Object#wait、Object#notify、Object#notify只能在同步控制方法或同步代码块(synchronized)中使用,否则即使能通过编译,但在运行时会抛出IllegalMonitorStateException异常,因为这些方法在调用时会操作锁,所以它必须先获取到锁。Thread#sleep与Thread#yield可以在非同步控制方法中调用,因为它们始终没有释放锁,更不用谈操作了。

针对Object#wait(long millisecs, int nanosecs)方法,注意nanosecs参数要在0-999999范围内(毫秒与纳秒单位换算是6个0),millisecs不能为负。

我们都知道每个对象都有一个关联的监视器,即通常所说的锁,除此之外,还具有一个关联的等待容器,很容易理解,里头存放的就是处于等待状态的线程(稍微纠结了一下,阻塞状态的线程不会处于该容器中,因为它是跟监视器有关的),也就是说当前线程调用Object#wait方法后,该线程会被添加到Object的等待容器中,并释放对象锁(不管此时有多少嵌套层级的锁都会得到释放,相当于将锁的计数减到0),处于等待容器中的线程不会执行任何其他指令。而当执行notify、notifyAll、interrupt或wait超时后,该线程可能会从等待容器中删除,在获取到对象锁后,该线程会使对象重新上锁(之前是多少嵌套,现在就会有多少嵌套,相当于做了恢复),在获到CPU时间片后从而继续执行。顺便提一下,对象内部持有对锁的计数(猜测),只有当该数量变成0后其他线程才能获取该对象锁。(理解这点概念很重要!!!否则概念多了容易乱)

notify无法保证在等待容量中选择哪个线程作为删除,notifyAll会将所有线程从等待容量中删除。

interrupt会在对象重新上锁(什么时候重新上锁上面提到过)后引发InterruptedException异常,在try-catch块中获取到的中断状态(isInterrupted)为false,更具体的信息可看方法说明。

如果线程在等待时既被通知(notify)又被中断(interrupt),则可能是先通知后中断,也有可能是先中断后通知。

到这里应该对线程的状态有所了解了,读者可以发现实际上线程并没有运行中的状态,而在操作系统层面,它却有执行中的状态,这是为何?一个方面,CPU为每个线程分配时间片进行调度,时间片一般是几十毫秒,当其中一个线程执行一个时间片后会切换到下一个线程,在切换前会保存当前线程的状态,以便下次切换回来时可以加载该线程的状态,这样子的一个过程称为上下文切换,很显然线程之间的切换是非常快的,那么此时去区分运行中与就绪状态是没有多大意思的,有可能上一秒你看到的是运行中状态,可实际上还没等你反应过来后就变成了就绪状态,也许你只能看到这两个状态在互相闪烁着;另外一个方面,Thread注释中说:处于可运行状态下的线程正在JVM中执行,但它可能正在等待来自于操作系统的其他资源,比如处理器、CPU、各种硬件,JVM把这些都视作资源,有东西在为线程服务,它就认为线程在执行。对于操作系统来说它的侧重点是CPU,它必须要明确每个线程的具体状态,否则对于可运行状态来说,它怎么知道线程是否是可以调度的(部分观点摘自其他人的文章)。最后在提一点,JVM设置线程的状态是为了防止已经启动的线程被重新启动。

数据结构


    public class Thread implements Runnable {

        // 注册本地方法,比如start0、stop0,至于做了什么只能说太过于底层了,不懂C、C++的可以忽略了
        private static native void registerNatives();
        static {
            registerNatives();
        }

        /**
         * 线程的名称
         * 为什么线程的名称要加上volatile关键词呢?
         * 首先volatile保证了内存的可见性,即其中一个线程修改了某个共享变量,其他的线程能够马上看到该变量修改后的值,更多的知识点将会另起文章阐述
         * 有可能多个线程共享该名称变量
         */
        private volatile String name;

        /**
         * 线程的优先级
         * 注意:对于不同的平台可能优先级不同,有的是3个优先级、有的是10个优先级,所以在设置优先级时最好写Thread.MAX_PRIORITY
         */
        private int priority;

        /**
         * 该线程是否是守护(后台)线程
         */
        private boolean daemon = false;

        /**
         * 线程要执行的任务,即最后会调用该任务的run方法 
         */
        private Runnable target;

        /**
         * 线程所属的线程组,一般情况下线程并未指定线程组的话默认是采用主线程的线程组,而主线程是从哪里的呢,这就要看JVM启动了...
         */
        private ThreadGroup group;

        /**
         * 类加载器
         */
        private ClassLoader contextClassLoader;

        /**
         * 访问控制,对访问控制进行操作或决定
         * 1. 决定是否允许还是拒绝对关键系统资源的访问 
         * 2. 特权代码,影响访问控制决定
         * 3. 可获取当前上下文,针对已保存的上下文做出来自不同上下文的访问控制决定
         */
        private AccessControlContext inheritedAccessControlContext;

        /**
         * 当线程没有指定线程名时,内部会帮助我们生成一个新名字-Thread-number,而其中的number会随着线程的增加而递增,如Thread-1、Thread-2
         */
        private static int threadInitNumber;

        /**
         * 每个线程都有自己的一个本地栈-ThreadLocal,ThreadLocal通过ThreadLocalMap来维护变量
         * ThreadLocalMap底层维护了一个数组,数组中维护了键值对的关系,其中键是当前ThreadLocal对象,也就是说,一个ThreadLocal只能绑定一个值,若是想绑定多个值的话就要定义多个ThreadLocal对象
         */
        ThreadLocal.ThreadLocalMap threadLocals = null;

        /**
         * 将当前线程的ThreadLocal设置到创建后的线程中,不知道使用的场景是哪里?
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

        /**
         * 设置栈大小,在更底层的代码中用到,该属性依赖于不同的平台会有不同的行为
         */
        private long stackSize;

        /**
         * 可中断对象,主要用来I/O阻塞的线程调用interrupt后,需要去唤醒selector
         */
        private volatile Interruptible blocker;

        /**
         * 未处理异常的默认处理器,即异常没有被捕获,通常情况下为null,即直接在控制台打印异常堆栈信息
         * 若设置了默认处理器,则所有的线程都会应用该默认处理器,而如果又同时设置了自定义处理器,则指定线程只会应用自定义处理器
         */
        private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

        /**
         * 异常未处理的自定义处理器,当未指定该自定义处理器后会走默认的处理器
         */
        private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

        /**
         * 记录线程ID
         */
        private long tid;

        /**
         * 为了生成线程ID,呈现递增趋势,一开始该值并不是0,因为JVM内部也会创建系统线程
         */
        private static long threadSeqNumber;

        /**
         * 线程的状态
         * 0-NEW  1-RUNNABLE 4-RUNNABLE 2-TERMINATED 16-WAITING  32-TIMED-WAITING 1024-BLOCKED
         * 其中1和4应该就是所谓的就绪与运行中,只不过哪个对应哪个就不可而知,操作系统的线程状态映射到JVM的线程状态
         */
        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-i,i是个整数
     */
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    /**
     * 初始化线程
     * 自动生成线程的名称
     * @param target 指定任务
     */
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    /**
     * 初始化线程
     * 自动生成线程的名称
     * @param target 指定任务
     * @param acc 访问控制
     */
    Thread(Runnable target, AccessControlContext acc) {
        init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    }

    /**
     * 初始化线程
     * 自动生成线程的名称
     * @param group 线程组
     * @param target 指定任务
     */
    public Thread(ThreadGroup group, Runnable target) {
        init(group, target, "Thread-" + nextThreadNum(), 0);
    }

    /**
     * 初始化线程,指定线程的名称
     * @param name 线程的名称
     */
    public Thread(String name) {
        init(null, null, name, 0);
    }

    /**
     * 初始化线程,指定线程的名称
     * @param group 线程组
     * @param name 线程的名称
     */
    public Thread(ThreadGroup group, String name) {
        init(group, null, name, 0);
    }

    /**
     * 初始化线程,指定线程的名称
     * @param target 指定任务
     * @param name 线程的名称
     */
    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }

    /**
     * 初始化线程,指定线程的名称
     * @param group 线程组
     * @param target 指定任务
     * @param name 线程的名称
     */
    public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
    }

    /**
     * 初始化线程,指定线程的名称
     * stackSize依赖于不同的平台会有不同的值,所以使用该构造函数时要小心点
     * @param group 线程组
     * @param target 指定任务
     * @param name 线程的名称
     * @param stackSize 栈大小 
     */
    public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
        init(group, target, name, stackSize);
    }


简单方法


    /**
     * 该方法主要使用在nio的selector中,这里是存储可中断的匿名类
     * 当在I/O阻塞中的线程调用interrupt时,需要去唤醒selector
     * 可全局搜索该方法的调用即可知道它的使用目的
     * @param b 可中断对象
     */
    void blockedOn(Interruptible b) {
        synchronized (blockerLock) {
            blocker = b;
        }
    }

    /**
     * 获取线程名称
     * @return 线程名称
     */
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /**
     * 获取线程ID
     * @return 线程ID
     */
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }

    /**
     * 获取当前线程的引用
     */
    public static native Thread currentThread();

    /**
     * 每个线程都有一个CPU时间片,该方法指在自愿放弃CPU时间片,让其他线程能够更快的执行
     */
    public static native void yield();

    /**
     * 使线程进入指定时间的休眠,该方法不会释放锁,可以在非同步控制方法或同步块内使用
     * @param millis 休眠指定时间,毫秒为单位
     */
    public static native void sleep(long millis) throws InterruptedException;

    /**
     * 使线程进入指定时间的休眠,该方法不会释放锁,可以在非同步控制方法或同步块内使用
     * @param millis 指定毫秒时间
     * @param nanos 指定纳秒时间
     */
    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) { //纳秒与毫秒的单位换算是6个0
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        sleep(millis);
    }

    /**
     * 初始化线程
     * @param g 线程组
     * @param target 指定任务
     * @param name 指定线程名称
     * @param stackSize 线程的栈大小
     */
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

    /**
     * 初始化线程
     * @param g 线程组
     * @param target 指定任务
     * @param name 指定线程名称
     * @param stackSize 线程的栈大小
     * @param acc 访问控制,如是否允许访问系统资源
     * @param inheritThreadLocals 是否将当前线程的inheritableThreadLocals设置到创建后的线程中,类似于继承
     */
    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) {
                g = security.getThreadGroup();
            }

            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        g.checkAccess(); //确定当前正在运行的线程是否有权修改此线程组

        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted(); //记录未启动的线程个数,即未调用start方法

        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;

        tid = nextThreadID(); //设置线程ID
    }

    /**
     * 启动线程,底层由JVM调用指定任务的run方法
     * 该方法只能调用一次
     */
    public synchronized void start() {
        
        if (threadStatus != 0) //threadStatus表示当前线程处于NEW状态
            throw new IllegalThreadStateException();

        group.add(this); //将当前线程添加到线程组,至少线程组到底做了什么可能需要另外起文章来探索

        boolean started = false;
        try {
            start0(); //启动线程
            started = true;
        } finally {
            try {
                if (!started) { //若启动失败的话,需要去线程组中将其移除掉
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                
            }
        }
    }

    /**
     * 启动线程,底层将C++的线程与Java的线程进行了绑定,至于C++中的线程又做了什么就不得而知了,能猜到应该是跟操作系统打交道了
     */
    private native void start0();

    /**
     * 执行任务
     * 若线程未调用start,直接调用run方法的话,那么就相当于执行一个方法,并未存在什么新线程
     */
    public void run() {
        if (target != null) {
            target.run();
        }
    }

    /**
     * 由JVM调用此方法,使线程在退出之前进行资源清理
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        target = null;
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

    /**
     * 终止线程,即使已经启动的线程也会马上被终止
     * 
     * 1. 该方法已经被废弃了,为什么会被废弃呢?官方定义如下:
     * stop本质上是不安全的. 终止一个线程会造成该线程持有的锁得到释放(当ThreadDeath异常向上传播到堆栈时,锁将被释放)
     * 如果之前由这些锁保护的任何对象处于不一致状态,其他线程可能立即查看这些状态不一致的对象. 这样子的对象被认为已受损.
     * 当线程操作受损对象时会导致不可预测的行为,该行为可能很难被检测到. 不像非受查异常(Exception),ThreadDeath默默地杀死了线程(简单来说,我们无法从ThreadDeath异常上得到任何的消息)
     * 因此,用户没有警告其程序可能已损坏,该损坏是不可预测的.
     * 举个例子:
     * synchronized void f() { 
     *       x = 1; 
     *       x = 2;
     *       x = 3;
     * }
     * 不调用stop方法的情况下是可以正常运行的,但是在调用了stop方法后,程序立马被停止了,此时我们并不知道程序执行到了哪里,是 x = 1还是 x = 2 还是 x = 3
     * 所以即使其他线程获取到锁之后也无法确定x的值,第一次执行是1,第二次执行有可能是2,这就导致了结果的不可预测,这就相当于执行中的程序在任意时刻突然被破坏了
     *
     * 
     * 2. 我不能捕捉ThreadDeath异常并修复受损的对象吗?
     *    线程可以在几乎任何地方抛出ThreadDeath异常,那我们怎么知道造成了对象的受损了,那就得慢慢分析每个抛出该异常的方法了.
     *    从第一个线程清除时(在catch或finally子句中),线程可以引发第二个ThreadDeath异常(最后要抛出ThreadDeath来确保线程被终止). 必须重复进行清理,直到成功.
     *
     * 3. 使用什么方式来代替stop?
     *    通过简单地修改变量的值来表明线程应该被终止,线程应定期检查该变量,如果该变量表明要终止运行,则应有序地从其run方法返回.
     *
     *    方式一:
     *    private volatile Thread blinker;
     * 
     *    public void stop() {
     *       blinker = null;
     *    }
     *
     *    public void run() {
     *      Thread thisThread = Thread.currentThread();
     *      while(blinker == thisThread){
     *         try {
     *              Thread.sleep(interval);
     *          } catch(InterruptedException e) {
     *              
     *          }
     *          repaint(); //业务逻辑
     *       }
     *    }
     *
     *    方式二:
     *    private final AtomicBoolean running = new AtomicBoolean(false);
     *    private int interval;
     *
     *    public ControlSubThread(int sleepInterval) {
     *       interval = sleepInterval;
     *    }
     * 
     * 
     *    public void stop() {
     *       running.set(false);
     *    }
     *
     *    public void run() { 
     *      running.set(true);
     *      while (running.get()) {
     *         try { 
     *             Thread.sleep(interval); 
     *         } catch (InterruptedException e){ 
     * 
     *         }
     *      }
     *    }
     *    
     */
    @Deprecated
    public final void stop() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            checkAccess();
            if (this != Thread.currentThread()) {
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }

        if (threadStatus != 0) {
            resume(); //唤醒挂起的线程
        }

        /**
         * 应用程序中不能去catch ThreadDeath的实例,若真要catch,则处理完逻辑之后必须在重新抛出ThreadDeath异常对象,以便确定线程被终止
         * 线程中可以自定义异常处理器,只不过该异常处理器在针对ThreadDeath时不会打印任何消息
         */
        stop0(new ThreadDeath()); 
    }

    /**
     * 中断线程
     * 1. 若是调用wait、join、sleep方法时导致线程处于等待状态,可调用此方法来中断线程,即从这些方法处返回并抛出InterruptedException异常,同时清除中断状态
     * 2. 若是调用I/O操作导致线程处于阻塞状态,可调用此方法来将通道关闭,同时会抛出一个异常,及设置中断状态
     * 3. 若是调用selector.select导致线程处于阻塞状态,可调用此方法来使select立即返回,并设置中断状态
     * 4. 如果上述条件均不成立,则将设置该线程的中断状态.
     * 中断未启动的线程不会产生任何影响.
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker; //上面咱们提到了关于blocker的作用,主要用于nio中的selector
            if (b != null) {
                interrupt0();
                b.interrupt(this);
                return;
            }
        }
        interrupt0();// 1、2、4
    }

    /**
     * 中断线程
     */
    private native void interrupt0();

    /**
     * 当前线程是否被中断,该方法会清除中断状态
     * 如果方法调用多次,则多次调用后会返回false,除了在多次调用后又发生中断
     * @return 是否被中断
     */
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    /**
     * 线程是否被中断,该方法不会清除中断状态
     */
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    /**
     * 线程是否被中断,是否清除中断状态取决于ClearInterrupted参数
     * @param ClearInterrupted 是否清除中断状态
     */
    private native boolean isInterrupted(boolean ClearInterrupted);

    /**
     * 该方法原先被设计为破坏线程,并未清理资源,也就是说线程持有的锁会继续持有,并未释放锁,幸好还未实现就已经被废弃了
     * 官方说若是实现了它,将与Thread#suspend方法一样容易导致死锁,锁未得到释放,其他线程无法访问
     */
    @Deprecated
    public void destroy() {
        throw new NoSuchMethodError();
    }

    /**
     * 线程是否处于活动状态中,活动状态表示:线程已启动(已调用start)且尚未终结
     */
    public final native boolean isAlive();

    /**
     * 挂起/暂停线程
     * 
     * 1. 该方法已废弃,为什么呢? 官方定义如下:
     * suspend本质上是容易死锁的. 当某个线程被挂起时,其任务还持有对关键系统资源的锁,其他线程都访问不了该资源.
     * 若有其他线程在调用resume之前尝试获取该锁,则会导致死锁(线程并未释放锁)
     *
     * 
     * 2. 使用什么方式来代替suspend?
     *
     *    private volatile boolean threadSuspended;
     *   
     *    public synchronized void mousePressed() {
     *        //业务逻辑
     *
     *        threadSuspended = !threadSuspended;
     *
     *         if (!threadSuspended) {
     *            notify();
     *         }
     *     }
     *
     *     public void run() {
     *
     *          wihle(true) {  //这里并未退出该任务
     *              try {
     *                  Thread.sleep(interval);
     *
     *                  if(threadSuspended) { //这样子的方式可以在线程没有被挂起的情况不用花费synchronized关键词引起的代价
     *                      synchronized(this) {
     *                          while(threadSuspended) {
     *                              wait();
     *                          }
     *                      }
     *                  }
     *              } catch(InterruptedException e) {
     *    
     *              }
     *          }
     *          //业务逻辑
     *    }
     *
     *
     * 3. stop与suspend结合
     *
     *    public void run() {
     *      Thread thisThread = Thread.currentThread();
     *      while (blinker == thisThread) { //调用stop后会正常退出
     *          try {
     *              Thread.sleep(interval);
     *
     *              synchronized(this) {
     *                 while (threadSuspended && blinker==thisThread)
     *                     wait();
     *             }
     *          } catch (InterruptedException e){
     *
     *         }
     *          //业务逻辑
     *      }
     *    }

    public synchronized void stop() {
        blinker = null;
        notify();
    }
     *
     */
    @Deprecated
    public final void suspend() {
        checkAccess();
        suspend0();
    }

    /**
     * 挂起线程
     */
    private native void suspend0();

    /**
     * 恢复线程
     * 该方法仅与suspend一起使用,但由于suspend容易造成死锁而被废弃了,故而resume也用不到了
     */
    @Deprecated
    public final void resume() {
        checkAccess();
        resume0();
    }

    /**
     * 设置线程的优先级
     * 1 < newPriority < g.maxPriority <= 10
     * @param newPriority 指定优先级
     */
    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);
        }
    }

    /**
     * 获取优先级
     * @return 优先级
     */
    public final int getPriority() {
        return priority;
    }

    /**
     * 设置线程的名称
     * 即使线程已经启动了依然还是可以设置线程的名称
     * @param name 指定线程的名称
     */
    public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) { // threadStatus != 0 表示线程已启动
            setNativeName(name);
        }
    }

    /**
     * 获取线程的名称
     * @return 线程的名称
     */
    public final String getName() {
        return name;
    }

    /**
     * 获取线程组
     * @return 线程组
     */
    public final ThreadGroup getThreadGroup() {
        return group;
    }

    /**
     * 获取当前线程组的活跃线程个数
     * 注意:线程组中还有子线程组,所以计算时也会包含子线程组
     * 该结果只是一个估算值,因为线程数一直在动态变化着,同时JVM也会有一些线程,所以可能会影响该计数结果
     * 该方法主要用来调试与监控
     * @return 线程组的活跃线程个数
     */
    public static int activeCount() {
        return currentThread().getThreadGroup().activeCount();
    }

    /**
     * 将当前线程组的活跃线程拷贝到指定数组中
     * 要严格确保指定数组的大小大于活跃线程的个数
     * @param tarray 指定数组
     * @return 线程组的活跃线程个数
     */
    public static int enumerate(Thread tarray[]) {
        return currentThread().getThreadGroup().enumerate(tarray);
    }

    /**
     * 计算线程的栈帧,何为栈帧? 这就涉及到JVM的知识了,笔者还在慢慢往上走呢.
     * 调用该方法时,线程应该被挂起,而suspend已被废弃了,故而该方法也没啥用了
     */
    @Deprecated
    public native int countStackFrames();

    /**
     * 当前线程最多等待指定时间
     * t1.join(time);
     * 这里指的当前线程并不是t1,而是执行这些所属的线程,也就是main线程,按照最上面的概念来说,应该是当前线程添加到了t1对象的等待容器中
     *
     * @param millis 指定毫秒时间
     */
    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); //等待状态下的线程若是调用interruput去中断的话则会直接抛出InterruptedException异常后恢复运行,若是使用notify的话,那么最终都会等待指定的millis时间才会恢复运行
                now = System.currentTimeMillis() - base;
            }
        }
    }

    /**
     * 当前线程最多等待指定时间
     * 若指定时间设置为0,则会一直等下去
     * 做了一些参数校验
     * @param millis 指定毫秒时间
     * @param nanos 指定纳秒数
     */
    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 static void dumpStack() {
        new Exception("Stack trace").printStackTrace();
    }

    /**
     * 根据传入的值来决定将线程变成后台线程还是用户线程
     * 当所有正在运行的线程都是后台线程时,JVM退出
     * 该方法必须在start之前调用
     * false-用户线程   true-后台线程
     * @param on 标志
     */
    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

    /**
     * 线程是否是后台(守护)线程
     * @return 是否是后台线程
     */
    public final boolean isDaemon() {
        return daemon;
    }

    /**
     * 当前线程是否有权限修改当前对象所属的线程,若没有权限则抛出异常
     */
    public final void checkAccess() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkAccess(this);
        }
    }

    /**
     * 打印线程的名称,优先级,线程组
     * @return 结果
     */
    public String toString() {
        ThreadGroup group = getThreadGroup();
        if (group != null) {
            return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]";
        } else {
            return "Thread[" + getName() + "," + getPriority() + "," + "" + "]";
        }
    }

    /**
     * 当前线程是否持有指定对象锁
     * true-表示当前线程持有指定对象锁
     * @return 是否持有指定对象锁
     */
    public static native boolean holdsLock(Object obj);

    /**
     * 获取线程的ID
     * @return 线程的ID
     */
    public long getId() {
        return tid;
    }

    /**
     * 获取当前线程的状态
     * @return 当前线程的状态
     */
    public State getState() {
        return sun.misc.VM.toThreadState(threadStatus);
    }

    /**
     * 设置未处理异常的默认处理器
     * 若设置了默认处理器,则所有的线程都将应用到,相当于全局处理器,而如果同时设置了自定义处理器,那么指定线程只会应用自定义处理器(可看dispatchUncaughtException方法)
     * @param eh 默认处理器
     */
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }
         defaultUncaughtExceptionHandler = eh;
     }

    /**
     * 获取未处理异常的默认处理器
     * 返回null表示没有设置
     * @return 默认处理器
     */
    public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    }

    /**
     * 获取未处理异常的自定义处理器
     * 若未设置自定义处理器,则走线程组的异常处理,线程组中会获取默认处理器,若也未设置,则打印堆栈信息到控制台,若设置了则走默认处理器
     * @return 自定义处理器
     */
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group;
    }

    /**
     * 设置自定义处理器
     * @param eh 自定义处理器
     */
    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

    /**
     * 下发异常到指定处理器上,该方法只会被JVM调用
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

    

总结

  • 创建线程有两个方式,一种是继承Thread,第二种是实现Runnable,第二种方式更为常见,且能够在只有一个任务实例的情况下创建多个线程。

  • 线程的6个状态-新建、可运行、阻塞、等待、超时等待、终止。

  • wait的实现机制是通过对象的等待容器,更具体的描述可参考概念。

  • 我们都知道每个线程都有一个本地栈,实际上是通过Thread对象中的threadLocals变量维护起来的,也就是说即使将ThreadLocal变量传入到不同的线程中也不会造成多线程之间共享同一个ThreadLocal,因为每个线程都有一个ThreadLocal对象,即上面提到的threadLocals变量,在为ThreadLocal设值时,每个线程都会检查当前线程下的threadLocals变量是否初始化了,若没有则初始化该变量。有一点需要注意下,ThreadLocal中通过维护一个Map来保存键值对的关系,而其中的键值就是当前的ThreadLocal对象,也就是说,将ThreadLocal传入到不同的线程中会造成它们的threadLocals变量所引用的键值是同一个,不过这丝毫不影响。

  • sleep、yield不会释放对象锁,可以在非同步控制方法或同步块中使用;wait、join、notify、notifyAll释放对象锁,必须在同步控制方法或同步块中使用,因为它们对锁进行了操作,故而需要先获取到锁才能操作。

  • stop:因为会造成数据的不完整,最终会导致不可预测的行为而废弃;suspend:因为始终都没有释放锁,容易造成死锁而废弃;destroy:官方说还未实现就已经废弃了,同样会造成死锁;resume:该方法仅与suspend一起使用,由于suspend已经被废弃了,那它也只好跟着牺牲了。

  • 未处理异常的默认处理器与自定义处理器的使用。

重点

线程的状态转变 wait/join/notify/notify/sleep/yield

参考资料

《Java编程思想》

《Java并发编程的艺术》

https://www.javazhiyin.com/52519.html

posted @ 2020-12-21 20:39  zliawk  阅读(94)  评论(0)    收藏  举报