美文网首页
Thread源码剖析

Thread源码剖析

作者: Felix_lin | 来源:发表于2019-01-03 17:16 被阅读8次

    对于线程Thread类的使用,可以说是java语言必备,但你是否真正意义上去剖析过他的内部结构,本文从概述的几个问题出发,一起进行源码阅读(本文基于Android-27中的Thread源码

    概述

    对常用的Thread做一次源码剖析,更好的去理解和使用它,看完之后你会明白的几个问题:

    1. 调用start发生了什么?多次调用start会怎么样?
    2. start和run方法的区别
    3. join和sleep的区别
    4. 什么是守护进程

    一、创建使用

    1. 初始化

    Thread构造函数

    内部调用--->init()方法

    java.lang.Thread#Thread()
    java.lang.Thread#Thread(java.lang.Runnable)
    java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable)
    java.lang.Thread#Thread(java.lang.String)
    java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String)
    java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String, int, boolean)
    java.lang.Thread#Thread(java.lang.Runnable, java.lang.String)
    java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String)
    java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
    

    init()方法指定四个参数:ThreadGroup,任务runable,线程名称,栈大小,其中部分参数初始值都是继承父线程的属性

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        Thread parent = currentThread();//获取创建thread的线程
        if (g == null) {
            g = parent.getThreadGroup();
        }
    
        g.addUnstarted();//在ThreadGroup中标记增加了一个未启动的线程,里面操作很简单,nUnstartedThreads++;
        this.group = g;
    
        this.target = target;
        this.priority = parent.getPriority();//继承父线程的等级
        this.daemon = parent.isDaemon();//继承父线程的属性:是否为守护进程
        setName(name);
    
        init2(parent);//保存一些常量参数,如上,给子线程调用
    
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
        tid = nextThreadID();
    }
    
    ...
    
    //线程 tid递增一个
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
    

    2. start方法

    • 在Android中,检测到再次调用start线程会抛出IllegalThreadStateException

        public synchronized void start() {
            
            // Android-changed: throw if 'started' is true
            if (threadStatus != 0 || started)
                throw new IllegalThreadStateException();
      
            //还记得上面init方法中,调用addUnstarted时,标记增加了未启动线程
            //这里调用add方法,将线程添加到系统线程数组,并且将未启动线程数减一,相当于移出
            group.add(this);
      
            started = false;
            try {
                nativeCreate(this, stackSize, daemon);
                //调用native方法启动线程,如果报错,则直接跳到finally执行,started为false,
                //启动失败,从group中移除,同时group中未启动线程数++
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        } 
      

    3. run方法

    //Thread实现的Runnable接口
    class Thread implements Runnable {
    
        ...
        
        //调用传入的Runnable的run方法
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    

    二、Thread阻塞

    1.join方法

    join方法用于等待线程执行完成,传入的时间单位为等待的最大时长,里面是一个 while (isAlive())循环函数,当不传入时间参数,则为永久等待直到线程结束,传入时间参数,当时间到达时会结束join方法

    public final void join(long millis) throws InterruptedException {
        synchronized(lock) {
            long base = System.currentTimeMillis();
            long now = 0;
    
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (millis == 0) {
                while (isAlive()) {
                    lock.wait(0);
                }
            } else {
                //循环,当达到最大等待时常,则跳出循环
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    lock.wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }
    }
     public final void join() throws InterruptedException {
        join(0);
    }
    //等待多少毫秒在加多少纳秒
    public final void join(long millis, int nanos)
    throws InterruptedException {
        synchronized(lock) {
        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);
        }
    }
    

    2.sleep方法

    sleep作用是使当前线程睡眠指定时间,其中几个关键点

    • 获取当前调用线程的lock:currentThread().lock;

    • 通过while (true)循环sleep当前线程,并检测睡眠时间达到传输参数时间,break当前循环

        public static void sleep(long millis, int nanos)throws InterruptedException {
            if (millis < 0) {
                throw new IllegalArgumentException("millis < 0: " + millis);
            }
            if (nanos < 0) {
                throw new IllegalArgumentException("nanos < 0: " + nanos);
            }
            if (nanos > 999999) {
                throw new IllegalArgumentException("nanos > 999999: " + nanos);
            }
      
            //当睡眠时间为0,先检测线程是否已经中断,是的话抛出异常,否则直接return
            if (millis == 0 && nanos == 0) {
                // ...but we still have to handle being interrupted.
                if (Thread.interrupted()) {
                  throw new InterruptedException();
                }
                return;
            }
      
            long start = System.nanoTime();
            long duration = (millis * NANOS_PER_MILLI) + nanos;
      
            获取当前线程的lock
            Object lock = currentThread().lock;
      
            // Wait may return early, so loop until sleep duration passes.
            synchronized (lock) {
                while (true) {
                    sleep(lock, millis, nanos);
      
                    long now = System.nanoTime();
                    long elapsed = now - start;
      
                    if (elapsed >= duration) {
                        break;
                    }
      
                    duration -= elapsed;
                    start = now;
                    millis = duration / NANOS_PER_MILLI;
                    nanos = (int) (duration % NANOS_PER_MILLI);
                }
            }
        }
        public static void sleep(long millis) throws InterruptedException {
            Thread.sleep(millis, 0);
        }
      
        @FastNative
        private static native void sleep(Object lock, long millis, int nanos)
            throws InterruptedException;
      

    3.sleep与join的区别

    1. join里面调用的wait方法,wait方法可以释放锁,而sleep方法是持有锁
    2. join(0)是一直等待线程执行完成,只有这个线程执行完后,才能执行其他线程,中间通过循环lock.wait(delay)实现,它是非静态方法,
    3. sleep是静态方法,通过currentThread获取当前线程的lock,它只能作用当前线程

    三、Thread终止

    1.stop方法

    stop方法以及被弃用,强行调用的话会抛出UnsupportedOperationException异常

     @Deprecated
    public final void stop() {
        stop(new ThreadDeath());
    }
    
      @Deprecated
    public final void stop(Throwable obj) {
        throw new UnsupportedOperationException();
    }
    

    2.interrupt方法

    部分内容引用一篇很详细的文章,戳-->《Java线程源码解析之interrupt》

    • interrupt的作用是中断线程,我们经常调用,interrupt的使用有几个注意点

    • 当线程处于wait,sleep,join等方法阻塞状态时,它会清除当前阻塞状态,并抛出InterruptedException异常

    • 在I/O通讯状态中调用interrupt,数据通道会被关闭,并将线程状态标记为中断,并抛出ClosedByInterruptException异常

    • 如果在java.nio.channels.Selector上堵塞,会标记中断状态,并马上返回select方法

    • Lock.lock()方法不会响应中断,Lock.lockInterruptibly()方法则会响应中断并抛出异常,区别在于park()等待被唤醒时lock会继续执行park()来等待锁,而 lockInterruptibly会抛出异常

    • synchronized被唤醒后会尝试获取锁,失败则会通过循环继续park()等待,因此实际上是不会被interrupt()中断的;

    • 一般情况下,抛出异常时,会清空Thread的interrupt状态,在编程时需要注意;

    //用来中断的IO通讯对象,在调用interrupt方法后会调用blocker的中断方法
    private volatile Interruptible blocker;
    
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();
    
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                nativeInterrupt();
                b.interrupt(this);
                return;
            }
        }
        nativeInterrupt();
    }
    

    四、线程的状态

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
    
    public State getState() {
        // get current thread state
        return State.values()[nativeGetStatus(started)];
    }
    
    • NEW:线程创建还未启动时状态
    • RUNNABLE:线程运行状态,包括一些系统资源等待,如:IO等待,CPU时间片切换等
    • BLOCKED:正在等待monitor lock的状态,比如:1. 即将进入synchronized方法或者块前等待获取锁的这个临界时期状态。2.调用wait方法释放锁之后再次进入synchronized方法或者块前的临界状态
    • WAITING:基于上个BLOCKED状态来说,WAITING就是拿到锁了,处于wait过程中的状态,注意它是特指无限期的等待,也就是join()或者wait()等,它是join或者直接wait方法当获取到lock执行后,处于等待notify的WAITING状态。
    • TIMED_WAITING:与上面WAITING相对,WAITING是指无限期的等待,TIMED_WAITING就是有限期的等待状态,包括join(long),wait(long),sleep(long)等。
    • TERMINATED:线程执行完成,run结束的状态
      image

    五、总结:回答上述问题

    1. 调用2次start时,看start源码中,里面判断如果当前线程状态和是否启动标记,if (threadStatus != 0 || started),如果已经启动则抛出IllegalThreadStateException异常,可以通过继承Thread类或者实现Runnable去开启线程,这样每次new了新的对象启动线程
    2. start是启动当前Thread线程,Thread实现了Runnable接口的run方法,当线程启动,run方法会被调用,Thread里面的Run会调用传入Runnable Target的run方法,达到实现我们自定义任务的目的。如果没有传入Runnable参数则do nothing
    3. join是等待线程执行完成,方法通过内部一个while(alive)的循环函数去实现wait等待,alive是一直检测线程的存活状态,它相当于,在那个线程执行join,即在哪个线程执行wait,调用的线程对象可以理解为lock对象,即调用了lock.wait(), sleep方法是一直持有锁的状态,同时sleep是静态方法,它通过currentThread获取当前线程的lock,并只能作用当前线程
    4. 守护线程意思是后台服务线程,比如垃圾回收线程,要理解它就知道另一个用户线程,用户线程是维持程序运行状态,或者说jvm存活的线程,如果用户线程都跑完了,那么不管守护线程是否运行,程序和jvm都会退出,当然此时,守护线程也会退出,由此可以看出守护线程和用户线程对于程序运行的相关性。由上述线程的init方法可以看出,子线程的创建会继承一些默认参数,包含是否为守护线程,它是低级别的线程,不依赖于终端,但是依赖于系统,与系统“同生共死”。

    相关文章

      网友评论

          本文标题:Thread源码剖析

          本文链接:https://www.haomeiwen.com/subject/ywkqrqtx.html