今天来看一下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. React 17 要来了,非常特别的一版

    写在前面 React 最近发布了v17.0.0-rc.0,距上一个大版本v16.0(发布于 2017/9/27)已经过去近 3 年了 与新特性云集的 React 16及先前的大版本相比,React 1 ...

  2. 动态数组java实现

    数组是一种顺序存储的线性表,所有元素的内存地址是连续的. 动态数组相对于一般数组的优势是可以灵活地添加或删除元素.而一般数组则受限于固定的内存空间.只能有限的添加元素 动态数组(Dynamic Arr ...

  3. magento 2 cronjob setup

    crontab -u magento_user -e */5 * * * * php /var/www/html/bin/magento cron:run >> /var/www/html ...

  4. 谱聚类的python实现

    什么是谱聚类? 就是找到一个合适的切割点将图进行切割,核心思想就是: 使得切割的边的权重和最小,对于无向图而言就是切割的边数最少,如上所示.但是,切割的时候可能会存在局部最优,有以下两种方法: (1) ...

  5. Windows下搭载虚拟机以及环境安装

    前言 最近回到家中进行赛前自主提升 模拟赛考虑到考试环境是NOI Linux 而大多数同学电脑环境为Windows 有同学想要模拟真实考试环境 但是NOI Linux的系统过于"阉割版&qu ...

  6. muduo源码解析4-exception类

    exception class exception:public std::exception { }; 作用: 实现了一个异常类,继承于std::exception,主要用于实现打印线程调用栈信息. ...

  7. Selenium中核心属性以及方法

    一.操作定位元素 selenium提供了定位元素的API,这些方法都被定义在webDriver类中,需要以find开头, 例如:find_Element_by_id('')

  8. 使用Arcgis时,在sde空间库常用的相关函数

    一.Oracle库中配置好sde空间库常见的场景 1.在sde库中创建表:community 创建表:community 字段:id(INTEGER), shape(ST_GEOMETRY) 2.往s ...

  9. 正则表达式断言精讲 Java语法实现

    目录 断言 1.2.3.1 情景导入 什么是断言 断言的语法规则 零宽断言为什么叫零宽断言 零宽 前行 负向 断言DEMO 断言的基础应用和实际用处 验证不包含 验证开头包含 验证开头包含且匹配到的数 ...

  10. 关于华为服务器 双路E52620安装系统时遇到的问题

    一   准备好u盘启动盘,centos7的,用UltraISO制作启动盘 双击图标 进入 点击启动下的刻录磁盘映像 几分钟之后制作完场 二 :按装centos7的系统 1 首先将 u盘插到服务器上 2 ...