Thread源码解析

2020-08-25 14:28:40 浏览数 (3)

我们上学的时候都知道线程有两种方式,要么继承Thread类,要么实现runable接口。根据我们上次对线程池的分析,发现我们对Thread类的理解还比较浅显。所以深入理解Thread成为我们以后学习线程池的基础。

代码语言:javascript复制
实现Runnable接口方式
public class ShowMessage implements Runnable{

    private String message="任务";
    @Override
    public void run() {
        System.out.println(message);
    }

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


继承方式
public class MyThread extends Thread{

    @Override
    public void run() {
        System.out.println("继承的任务");
    }

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

通过上述的例子我们大致的信息是Runnable的run是线程执行的核心。那么到底是这样吗?我们先看一下Thread类和Runnable接口的关系,在没有查看源代码的前提下我个人觉得Thread肯定是实现了Runnable接口的。

而通过查看Runnable接口定义的方法,我们发现其实Runnable接口只有一个方法那就是run方法。

那么我们是不是可以得出的结论:thread类相当于一个工具类,专门用来将执行runnable的任务,当然由于Thread自身实现了Runnable接口所以它本身内部也可以直接定义任务。那么问题就变成了Thread如何开辟线程并进行运行的了,但是相对于高级语言来说它肯定是调用的操作系统提供的api了,所以说我们前进的步法可能就止于逻辑的合理性上了。

在上边的Thread类的结构图上,我们看到类的基本结构。我们回顾一下我们上一文中的ThreadPoolExecutor的任务队列,通过我们的分析,难么任务队列也必然就是一系列实现了Runnable接口的任务,而Thread类就全盘交付给了ThreadFactory了。其实分析到这里,我们基本上都不用看Thread类就可以去尝试看ThreadPoolExecutor的实现了。可是既然我们来了那就多多少少看看Thread类吧,毕竟Thread类维护线程的状态还是比较麻烦的。

根据类的实例化过程我们首先注意到的是registerNatives方法,这是native方法显然这块应该是将这个线程注册到哪里?根据一般规则,肯定是操作系统了。目前来说就这样理解吧。

代码语言:javascript复制
    private static native void registerNatives();
    static {
        registerNatives();
    }

我们看到所有的初始化方法最终都调用的init方法

代码语言:javascript复制
    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();


        //设置线程组
        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
        tid = nextThreadID();
    }

但是其中最重要的方法是native,所以parent就是已经创建好的线程了。

代码语言:javascript复制
public static native Thread currentThread();

看到上述代码已经将线程所需要的限制条件进行设置,那么下面的方法就是在条件范围内玩逻辑了

那么我们看看start()方法内在的实现逻辑。

代码语言:javascript复制
public synchronized void start() {
        //如果线程的状态不是新建的就抛出异常,因为start肯定是新的线程状态也就是0了
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        //上边在初始化的过程中,我们发现只是进行线程组的赋值,而不是添加,真正添加到线程组的操作是在启动线程的时候进行的
        group.add(this);
        boolean started = false;
        try {
        //调用的native方法启动线程
            start0();
            //设置启动成功
            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 */
            }
        }
    }
   //剔除启动失败的线程
    void threadStartFailed(Thread t) {
        synchronized(this) {
            remove(t);
            nUnstartedThreads  ;
        }
    }

但是启动线程的时候我们并没有发现那个方法调用了run方法,显然是start0在native层面上掉用的run方法。而run方法的本质是runnable接口。所以任务和线程是分开的。也验证了我们上述的说法。

代码语言:javascript复制
 private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

线程中断的时候是通过加锁的方式进行的,防止多个并发线程去竞争中断其他线程,interrupt0也是调用的native方法。

代码语言:javascript复制
   public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();


        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
代码语言:javascript复制
获取存活线程的数量
public static int activeCount() {
        return currentThread().getThreadGroup().activeCount();
    }

//在join方法中,采用wait阻塞的方式让线程在指定时间内休眠

代码语言:javascript复制
    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;
            }
        }
    }

在线程优先级上

代码语言:javascript复制
    //最小优先级
    public final static int MIN_PRIORITY = 1;


    //默认的优先级
    public final static int NORM_PRIORITY = 5;


    //最大的优先级
    public final static int MAX_PRIORITY = 10;

线程的五种状态

新建:当我们new Thread()就相当于创建了一个线程,这个线程向下进行注册,那么这个线程就处于在编模式,但并没有启动。那么这个这样的状态叫做新建。

就绪:当线程调用start0方法并执行玩start方法返回成功,但并没有执行run方法中的代码片段。而且线程有很多,但是cpu的核数是有限的。所以没有执行run方法的线程称之为就绪。 通过我们在start方法的学习,如果start成功了那么线程组就将这个线程添加到线程组,否则就剔除掉。那么是否可以说明在线程组中注册的线程都曾经历过就绪态?

运行:当线程获得cpu时间片段并执行run方法的时候称之为运行态。

阻塞:正在运行run方法的线程由于某种原因让出cpu让其他就绪态的线程使用cpu资源。

死亡:run方法执行结束或者run方法抛出了异常没有被处理,并跳出了run方法导致的线程猝死。另外isAlive方法可以用来判断线程是否是活着的,在就绪、阻塞和运行态时候会返回true,其他状态返回false。

0 人点赞