今天来看一下Thread和ThreadLocal类的源码。

一、Thread

(1)首先看一下线程的构造方法,之后会说每种参数的用法,而所有的构造函数都会指向init方法


//空构造创建一个线程
Thread()
//传入Runnable对象创建一个线程
Thread(Runnable target)
//传入Runnable对象和线程名创建一个线程
Thread(Runnable target, String name)
//传入线程名创建一个线程
Thread(String name)
//传入线程组和Runnable对象创建一个线程
Thread(ThreadGroup group, Runnable target)
//传入线程组、Runnable对象和线程名创建一个线程
Thread(ThreadGroup group, Runnable target, String name)
//传入线程组、Runnable对象、线程名和栈大小创建一个线程
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
//传入线程组和线程名创建一个线程
Thread(ThreadGroup group, String name)

(2)常用构造

最常用的构造方法是下面这个,其默认有一个生成线程名的函数。

Thread(Runnable target) 

public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}

(3)线程的初始化方法

 /**
* @param g 线程组,每个线程都会属于一个线程组
* @param target 线程会执行Runnable对象的run方法
* @param name 线程名
* @param stackSize 栈深度,默认是0
* @param acc 获取上下文,其实是获取classloader
* @param inheritThreadLocals 是否从继承的本地线程的值用于构造线程
*/
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;
//父线程,比如在Main方法中启动一个线程,就是Main线程
Thread parent = currentThread();
g.addUnstarted();
this.group = g;
//是否为守护线程,线程分为用户线程和守护线程,只要还有一个用户
//线程在跑,守护线程就不会挂掉。如果没有用户线程了,守护线程
//会和JVM一起退出
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; /* Set thread ID */
tid = nextThreadID();
}

(4)线程的start方法

【注意】只可执行一次start方法,不可重复执行

//threadStatus是用volatile修饰的,保证内存可见性
private volatile int threadStatus = 0;
public synchronized void start() {
//threadStatus是线程状态,一开始是0,启动一次之后就是其他值
if (threadStatus != 0)
throw new IllegalThreadStateException(); group.add(this); boolean started = false;
try {
//调用本地方法,启动线程
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 */
}
}
}

启动两次的案例:

第一次调用start方法:

第二次调用start方法:此时就会抛出IllegalThreadStateException

(5)activeCount()方法


//获取当前线程组存活线程数量
public static int activeCount() {
return currentThread().getThreadGroup().activeCount();
}

可以在主线程中用来等待其他线程完成,例如

//而isInterrupted是判断线程是否已被中断
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);

(6)interrupt、interrupted、isInterruped方法

 //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();
}
//而isInterrupted是判断线程是否已被中断
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);

当用isInterrupted返回为true的时候,可以用来中断线程


//此方法返回线程是否有“中断”标记,调用之后线程的“中断”标记会被去掉
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}

下面是中断线程的例子:


Thread a = new Thread(new Runnable() {
@Override
public void run() {
if (Thread.currentThread().isInterrupted()) {
System.out.println("【"+Thread.currentThread().getName()+"】当前线程数量"+Thread.activeCount());
System.out.println("线程被中断");
return;
}
while (true) {
}
}
},"a");
a.start();
a.interrupt(); try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("【Main】当前线程数量"+Thread.activeCount());

运行结果:

(7)线程状态

public enum State {
//刚创建线程还未调用start方法
NEW,
//调用start方法后,正在执行run方法
RUNNABLE,
//阻塞,等待其他线程释放监视器
BLOCKED,
//等待,等待其他线程调用notify或notifyAll方法
WAITING,
//等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TIMED_WAITING,
//已退出
TERMINATED;
}

二、ThreadLocal

此类里面可携带私有变量,这个变量是私有的,其他线程不可见。以下是两种创建方法并且设置值。

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(new Supplier<Integer>() {
@Override
public Integer get() {
return 1;//设置私有变量值
}
});

ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
//设置私有变量值
threadLocal.set(1);

上面是设置值,那如何获取值呢?

//获取私有变量值
threadLocal.get()

如何从内存中删除这个ThreadLocal呢?

//将当前ThreadLocal变量从内存用删除
threadLocal.remove();

删除之后再次获取就是为null。

(1)从设置值看底层结构


public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

可以看到,底层是一个ThreadLocalMap类,继续点进去ThreadLocalMap,其结构如下


ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private static final int INITIAL_CAPACITY = 16;
//初始值是10
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//计算index
int i = key.threadLocalHashCode & (len-1);
//从下标为i的一直往下,如果不为空,则替换值
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) {
replaceStaleEntry(key, value, i);
return;
}
}
//设置值
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//重新hash,里面有扩容的方法
rehash();
} private void rehash() {
expungeStaleEntries(); // 当元素数量大于(threshold - threshold / 4),就会进行扩容
//,当threadhold为10的时候就是8,
if (size >= threshold - threshold / 4)
resize();
} //扩容
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
//两倍扩容
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0; for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
//重新设置threadhold的值
setThreshold(newLen);
size = count;
table = newTab;
}

底层是一个Entry数组,初始化容量是16,扩容是两倍,那Entry是什么呢?

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

entry是弱引用的key-value对象,其用ThreadLocal的引用作为key,将私有变量值设置为value。之前的垃圾收集机制一篇说过弱引用对象只能存活到下一次垃圾回收前。但是注意,它被回收只会被回收掉key,也就是ThreadLocal,但是如果value在别的地方引用的话,就不会被回收,这样会造成内存溢出的情况。

例如:

jvm变量设置-Xmx10m -Xms10m
value在list中保持引用,内存溢出

value没在list中保持引用,一直运行

(2)get方法


public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//存在则返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//不存在,则先将值设为NULL,然后返回
return setInitialValue();
} private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

(3)remove方法

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
} 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;
}
}
} //将entry的引用置为null
public void clear() {
this.referent = null;
}

=======================================================

我是Liusy,一个喜欢健身的程序员。

欢迎关注【Liusy01】,一起交流Java技术及健身,获取更多干货。

Thread、ThreadLocal源码解析的更多相关文章

  1. Java 8 ThreadLocal 源码解析

    Java 中的 ThreadLocal是线程内的局部变量, 它为每个线程保存变量的一个副本.ThreadLocal 对象可以在多个线程中共享, 但每个线程只能读写其中自己的副本. 目录: 代码示例 源 ...

  2. Thread类源码解析

    源码版本:jdk8 其中的部分论证和示例代码:Java_Concurrency 类声明: Thread本身实现了Runnable接口 Runnable:任务,<java编程思想>中表示该命 ...

  3. ThreadLocal源码解析

    主要用途 1)设计线程安全的类 2)存储无需共享的线程信息 设计思路 ThreadLocalMap原理 1)对象存储位置-->当前线程的ThreadLocalMap ThreadLocalMap ...

  4. 一步一步学多线程-ThreadLocal源码解析

    上网查看了很多篇ThreadLocal的原理的博客,上来都是文字一大堆,费劲看了半天,大脑中也没有一个模型,想着要是能够有一张图明确表示出来ThreadLocal的设计该多好,所以就自己看了源码,画了 ...

  5. ThreadLocal源码解析-Java8

    目录 一.ThreadLocal介绍 1.1 ThreadLocal的功能 1.2 ThreadLocal使用示例 二.源码分析-ThreadLocal 2.1 ThreadLocal的类层级关系 2 ...

  6. ThreadLocal源码解析,内存泄露以及传递性

    我想ThreadLocal这东西,大家或多或少都了解过一点,我在接触ThreadLocal的时候,觉得这东西很神奇,在网上看了很多博客,也看了一些书,总觉得有一个坎跨不过去,所以对ThreadLoca ...

  7. Java ThreadLocal 的使用与源码解析

    GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/ ThreadLocal 主要解决的是每个线程绑定自己的值,可 ...

  8. 【Java源码解析】Thread

    简介 线程本质上也是进程.线程机制提供了在同一程序内共享内存地址空间运行的一组线程.对于内核来讲,它就是进程,只是该进程和其他一下进程共享某些资源,比如地址空间.在Java语言里,Thread类封装了 ...

  9. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

随机推荐

  1. docker入门1-docker container

    image和container介绍 一个image是一个可被docker执行的包,它包括程序运行的所有东西,包括代码,运行时,库,环境变量和配置文件. 一个container是image在内存中的运行 ...

  2. 提升布局能力!理解 CSS 的多种背景及使用场景和技巧

    CSS background是最常用的CSS属性之一.然而,并不是所有开发人员都知道使用多种背景.这段时间都在关注使用多种背景场景.在本文中,会详细介绍background-image`属性,并结合图 ...

  3. 利用JavaScript当鼠标点击导航时改变背景

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. Java多线程_同步工具CyclicBarrier

    CyclicBarrier概念:CyclicBarrier是多线程中的一个同步工具,它允许一组线程互相等待,直到到达某个公共屏障点.形象点儿说,CyclicBarrier就是一个屏障,要求这一组线程中 ...

  5. 面试:为了进阿里,死磕了ThreadLocal内存泄露原因

    前言 在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露.强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这 ...

  6. JAVA开源软件的技术选型--开源软件诞生2

    技术准备--第2篇 用日志记录“开源软件”的诞生 赤龙ERP开源地址,点亮星标,支持一下,万分感谢 码云:https://gitee.com/redragon/redragon-erp github: ...

  7. Mac搭建appium环境

    1.安装brew 查看是否已经装上brew,终端输入命令:brew --version,已经装上的就不用再装了: 如果没有安装,终端输入命令:ruby -e "$(curl -fsSL ht ...

  8. windows设置定时执行脚本

    如果你写了一些Python程序,想要在特定的时间进行执行,例如你想让一段爬虫程序在每天的上午10点执行一次,那么我们就可以来使用windows自带的定时任务进行设置.由于Windows系统,无法使用L ...

  9. JS开发必须知道的41个技巧

    JS是前端的核心,但有些使用技巧你还不一定知道:本文梳理了JS的41个技巧,帮助大家提高JS的使用技巧: Array 1.数组交集 普通数组 const arr1 = [, , , , , ,],ar ...

  10. 【小白学PyTorch】6 模型的构建访问遍历存储(附代码)

    文章转载自微信公众号:机器学习炼丹术.欢迎大家关注,这是我的学习分享公众号,100+原创干货. 文章目录: 目录 1 模型构建函数 1.1 add_module 1.2 ModuleList 1.3 ...