Thread源码解析

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

实现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方法显然这块应该是将这个线程注册到哪里?根据一般规则,肯定是操作系统了。目前来说就这样理解吧。

    private static native void registerNatives();
    static {
        registerNatives();
    }

我们看到所有的初始化方法最终都调用的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 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就是已经创建好的线程了。

public static native Thread currentThread();

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

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

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接口。所以任务和线程是分开的。也验证了我们上述的说法。

 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方法。

   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();
    }
获取存活线程的数量
public static int activeCount() {
        return currentThread().getThreadGroup().activeCount();
    }

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

    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 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。

本文分享自微信公众号 - 程序员备忘录(gh_a84f9a607848),作者:tianjl

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 线程池参数详解

    我们知道JUC提供了丰富的并发工具类,其中类似于组的结构叫做线程池。就是说我们可以将我们需要运行的线程加入到这个组内,然后通过启动线程池来执行加入到线程池的所有...

    程序员_备忘录
  • ReentrantLock源码解析

    在java编程中,经常需要多代码进行加锁来防止多线程可能引起的数据不一致。而锁的类型有公平锁和非公平锁。公平锁的意义就是按照顺序,而非公平锁则是相反的。也就是说...

    程序员_备忘录
  • CAS机制是什么?

    因为多核CPU的存在,总是需要保障程序高性能的利用计算机的CPU资源,并要保障计算结果与预期的一致。因此常用的方式就是加锁方式。就是占用->占用结束->释放的...

    程序员_备忘录
  • 优化指南,详解 Tomcat 的连接数与线程池

    在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。

    java思维导图
  • 详解tomcat的连接数与线程池

    在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。

    小柒2012
  • 详解 Tomcat 的连接数与线程池

    前言 在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。 在前面的文章 详...

    Java高级架构
  • 干货 | Tomcat 连接数与线程池详解

    在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。

    Java技术栈
  • iOS多线程编程之一——NSThread线程管理

    NSTread是iOS中进行多线程开发的一个类,其结构逻辑清晰,使用十分方便,但其封装度和性能不高,线程周期,加锁等需要手动处理。

    珲少
  • 并发编程-synchronized关键字大总结

    可重入(可以避免死锁、单个线程可以重复拿到某个锁,锁的粒度是线程而不是调用)、不可中断(其实也就是上面的原子性)

    Wizey
  • 干货|Tomcat 连接数与线程池详解前言

    在使用tomcat时,经常会遇到连接数、线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector)。

    美的让人心动

扫码关注云+社区

领取腾讯云代金券