从零开始的源码分析(Thread篇)

前言

今天约了招银的面试在10号早上,所以感觉需要加快一点进度了,接下来花两天时间搞定线程和线程池的内容,然后花一两天时间搞定锁这一块的源码,最后再把Mysql和Redis、JVM的相关内容复习一些,进行一个查漏补缺。

Thread源码

Thread的一般有两种实现方式(翻译自Thread类的注释):第一种,继承Thread类,第二种继承Runnable接口。
当然事实上在1.5中增加callable接口用于弥补Thread和runnable接口不能获取执行结果的问题。

接口

我们点开Thread类,可以发现实际上Thread类也实现了Runnable接口,那么这个Runnable接口里面有什么呢?

public class Thread implements Runnable 

其实里面什么都没有就只有一个run的抽象方法,@FunctionalInterface是函数式接口,java1.8引入,说白了就是只包含了一个抽象方法的接口。这个注解的存在意味着我们可以使用lambda表达式来创建一个线程。
根据代码上面的注释,我们可以了解到当一个对象实现了接口Runnable的时候是用来创建一个线程的。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

成员变量

Thread的成员变量非常多,而且有一些百度了一下也没找到是干嘛的,所以这边还是挑一些重要的讲一下吧,总的来说比较重要的就是ThreadLocalMap、target、priority和status。

//线程名字
private volatile String name;
//优先级
private int            priority;
//没注释,没百度到,也没找到引用,不知道干嘛的
private Thread         threadQ;
//JVM中的JavaThread指针
private long           eetop;

/* 是否单步执行此线程 */
private boolean     single_step;

/* 是否是守护线程 */
private boolean     daemon = false;

/* 虚拟机状态 */
private boolean     stillborn = false;

/* 传进来的对象 */
private Runnable target;

/* 线程的组 */
private ThreadGroup group;

/* 线程的上下文加载器,可以用来加载一些资源 */
private ClassLoader contextClassLoader;

/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;

/* 给匿名线程命名的整数,匿名线程会被命名为Thread-1这类名字,这个int会自增 */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

/*ThreadLocal的Map用于保存变量副本,后面会提到 */
ThreadLocal.ThreadLocalMap threadLocals = null;

/* InheritableThreadLocal用于子线程能够拿到父线程往ThreadLocal里设置的值 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

/*给线程分配的栈大小*/
private long stackSize;

/*
 * 本地线程终止之后的专用状态
 */
private long nativeParkEventPointer;

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

/* 用于生成线程ID */
private static long threadSeqNumber;

/* 线程状态 */
private volatile int threadStatus = 0;

线程状态和优先级

先说线程的优先级吧,线程的优先级并非采用的是线程状态的枚举类,而是在成员变量中设置了默认值,下限值和上限值,可以通过调用Thread.setPriority()方法来设定优先级,数值在1~10之间,如果不在这区间内会直接抛出异常,默认是5。

 /**
  * The minimum priority that a thread can have.
  */
 public final static int MIN_PRIORITY = 1;
/**
  * The default priority that is assigned to a thread.
  */
 public final static int NORM_PRIORITY = 5;
 /**
  * The maximum priority that a thread can have.
  */
 public final static int MAX_PRIORITY = 10;

在java中的线程状态一共有6种,不同于操作系统中定义,java中把运行状态和就绪状态合称为可运行状态。

public enum State {
    /**
     * 初始状态,还没有start的线程
     */
    NEW,
    /**
     * 可运行状态,出于运行或者就绪状态的线程
     */
    RUNNABLE,
    /**
     * 线程在等待monitor锁(synchronized关键字)
     */
    BLOCKED,
    /**
     * 无时间限制的等待,通过调用wait、join等方法进入,主要是等待其他线程进行一些		
     * 操作,不同于等待资源的阻塞状态。
     */
    WAITING,
    /**
     * 有时间限制的等待状态,通过sleep,wait(time),join(time)等方法进入
     */
    TIMED_WAITING,
    /**
     * 终止状态,表示线程已经完成执行。
     */
    TERMINATED;
}

构造函数

Thread的构造函数一共有9个之多,但是都是调用的init方法,这里就只列出一个构造方法。在这个参数最多的构造方法中,可以指定线程的分组、任务、名字和栈的大小。

public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, 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) {
            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);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

start()和run()

线程的start方法和run方法是两个概念,start是指这个线程可以进入运行状态,调用start之后线程会开始执行这个方法(不一定是立刻执行,要等待cpu调度),进入了runnable状态。
如果调用run的话相当于直接调用了这个target的方法,这个过程中并没有启动这个线程,还是在原来的线程上执行。

public synchronized void start() {
	//判断一下线程的状态,如果不是new状态则说明已经start了,抛出异常
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
	//向线程组中添加该线程
    group.add(this);

    boolean started = false;
    try {
    	//start0是一个本地方法,用于执行线程
        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 */
        }
    }
}
private native void start0();
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

exit()

exit()方法其实没有什么好说的,当线程完成run()方法之后就会执行这个方法,用于清空所有的内容,方便GC。

 /**
  * This method is called by the system to give a Thread
  * a chance to clean up before it actually exits.
  */
 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;
 }

yield()、wait()、sleep()和join()

wait和sleep方法都是本地方法,前者是在object类中的方法,让出cpu的同时释放资源,而后者则是在Thread类中的本地方法,让出cpu的同时不会释放资源。

public static native void sleep(long millis) throws InterruptedException;
//在java.lang.Object中
public final native void wait(long timeout) throws InterruptedException;
//也是一个本地方法,表示让其他线程先执行,不确保能够释放cpu
public static native void yield();

虽然join方法重载了多个但是最后都会调用到这个join方法上,可以看到最后其实调用的还是wait方法。

//使用synchronized进行修饰的方法锁定了调用这个方法的对象
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;
        }
    }
}

interrupt()

调用interrupt()相当于告诉线程需要进入终止状态。
interrupt()之前采用的是stop()方法,这个方法比较暴力,直接停止线程,容易造成一些错误。
interrupt()则是仅仅修改了线程的标志位,希望程序员自己根据这个标志位选择何时终止线程。如果对阻塞的线程调用interrupt()方法则会抛出异常。

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();
}

ThreadLocal

ThreadLocal是指线程保存的变量的副本,每个线程可以单独的操作这些变量而互不影响。

构造方法

ThreadLocal的构造方法比较特殊,里面什么都没有。主要还是因为ThreaLocal虽然看上去是一个存储着不同线程对象的类,实际上真正的对象都存储在线程自身的内部ThreadLocalMap中,get和set方法也是先获取线程的中map。
所以ThreadLocal实质上只是提供了map类、get、set方法,自身其实不保存任何内容。

public ThreadLocal() {
}

ThreadLocalMap类

ThreadLocalMap是一个内部类,和之前的HashMap有比较高的相似度。同样的是采用Entry的键值对,都是采用数组实现,如果数组中的元素超过了负载上限则需要进行扩容。
但是这个内部类并没有实现Map接口,在哈希冲突的时候采用方法也有一些差别。

 static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        private Entry[] table;
        private int size = 0;
        private int threshold; // Default to 0
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
}

set方法

ThreadLocal的set方法实际上是调用的map的set方法,所以我们直接来看map的set方法。

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.
    Entry[] tab = table;
    int len = tab.length;
    //计算哈希
    int i = key.threadLocalHashCode & (len-1);
    //当发现哈希地址冲突的时候采用的是线性探测法
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
        	//如果k==null说明这个key已经被回收了
        	//所以进行替换,顺便清楚其他的弱引用
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //清除之后判断一下长度,如果达到负载上限则需要扩容。
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

get方法

get方法调用的是map中的getEntry方法,相比较set方法,get方法会稍微简单一些。
简单来说就是计算一下key的哈希,然后看一下对应位置的对象是否是想要的对象。
如果不是说明可能发生了冲突,向数组的下一个地址移动,直到数组元素为空或者找到对象。

private Entry getEntry(ThreadLocal<?> key) {
	int i = key.threadLocalHashCode & (table.length - 1);
	Entry e = table[i];
	//当前元素就是想要的对象,或者为空,直接返回
	if (e != null && e.get() == key)
	    return e;
	else
	//向后查找
	    return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
        	//清除无效的entry
            expungeStaleEntry(i);
        else
        	//不断的向后找
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

remove()

remove()的原理其实和get差不多。

/**
 * Remove the entry for key.
 */
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

后记

今天主要总结了一下关于Thread类的源码,通过这次总结还是有了不少收获,从面经资料上看的只是原理,但是具体的实现方法还是有很多不同的讲究。
明天需要花点力气总结下线程池的内容了,相比较线程的内容,线程池在面试中问到的概率应该会更加高一些,可以扣的细节也更加多。


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_33241802/article/details/107113246