Thread、ThreadLocal源码解析
今天来看一下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源码解析的更多相关文章
- Java 8 ThreadLocal 源码解析
Java 中的 ThreadLocal是线程内的局部变量, 它为每个线程保存变量的一个副本.ThreadLocal 对象可以在多个线程中共享, 但每个线程只能读写其中自己的副本. 目录: 代码示例 源 ...
- Thread类源码解析
源码版本:jdk8 其中的部分论证和示例代码:Java_Concurrency 类声明: Thread本身实现了Runnable接口 Runnable:任务,<java编程思想>中表示该命 ...
- ThreadLocal源码解析
主要用途 1)设计线程安全的类 2)存储无需共享的线程信息 设计思路 ThreadLocalMap原理 1)对象存储位置-->当前线程的ThreadLocalMap ThreadLocalMap ...
- 一步一步学多线程-ThreadLocal源码解析
上网查看了很多篇ThreadLocal的原理的博客,上来都是文字一大堆,费劲看了半天,大脑中也没有一个模型,想着要是能够有一张图明确表示出来ThreadLocal的设计该多好,所以就自己看了源码,画了 ...
- ThreadLocal源码解析-Java8
目录 一.ThreadLocal介绍 1.1 ThreadLocal的功能 1.2 ThreadLocal使用示例 二.源码分析-ThreadLocal 2.1 ThreadLocal的类层级关系 2 ...
- ThreadLocal源码解析,内存泄露以及传递性
我想ThreadLocal这东西,大家或多或少都了解过一点,我在接触ThreadLocal的时候,觉得这东西很神奇,在网上看了很多博客,也看了一些书,总觉得有一个坎跨不过去,所以对ThreadLoca ...
- Java ThreadLocal 的使用与源码解析
GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/ ThreadLocal 主要解决的是每个线程绑定自己的值,可 ...
- 【Java源码解析】Thread
简介 线程本质上也是进程.线程机制提供了在同一程序内共享内存地址空间运行的一组线程.对于内核来讲,它就是进程,只是该进程和其他一下进程共享某些资源,比如地址空间.在Java语言里,Thread类封装了 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
随机推荐
- Datawhale学数据分析第一章
需要用到的基础知识pandas基础知识参考1,2章https://github.com/datawhalechina/joyful-pandas 1.导入数据tsv 制表符作为分隔符的字段符csv 逗 ...
- Magento 2 instantiate object by Factory Objects
magento2的Factory Objects是用来实例化non-injectable classes,目前还不知道什么叫non-injectable classes. 可以用它来实例化Helper ...
- Python的pyttsx3安装失败的解决方案
尝试更新安装工具,然后重试安装: pip install -U setuptools pip install pyttsx3 如果仍不能解决您的问题,您也可以尝试指定pyttsx3的版本 pip in ...
- 企业级Registry仓库Harbor的部署与简介
Harbor 是Vmware公司开源的企业级Docker Registry管理项目,开源项目地址:https://github.com/vmware/harbor Harbor的所有组件都在Docke ...
- Java数据结构——图的基本理论及简单实现
1. 图的定义图(graph)是由一些点(vertex)和这些点之间的连线(edge)所组成的:其中,点通常被成为"顶点(vertex)",而点与点之间的连线则被成为"边 ...
- “大地主”IPV6的邻居发现BD
引入 因为当初设计IPv4的时候,没有考虑到网络发展的速度这么快,到今现在IPv4有很多不足,32位的 IPv4地址不够用,现在128位的IPv6能完全够用,据说可以地球上每一粒沙子都分配一个地址,而 ...
- PyTorch迁移学习-私人数据集上的蚂蚁蜜蜂分类
迁移学习的两个主要场景 微调CNN:使用预训练的网络来初始化自己的网络,而不是随机初始化,然后训练即可 将CNN看成固定的特征提取器:固定前面的层,重写最后的全连接层,只有这个新的层会被训练 下面修改 ...
- React技术实践(1)
随着系统越来越庞大,前端也变得越来越复杂,因此,构建一套组件化的前端变得很重要了. 之前一直在使用Asp.net来进行前端的组件化,Asp.net组件化有个很大的缺陷,就是和后台代码绑定太紧密了,不符 ...
- Android开发之去掉listview的点击效果,一行代码间接粗暴,解决你的问题。
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 Android开发之去掉listview的点击效果,一行代码间接粗暴,解决你的问题. 当你在用list ...
- iOS审核被拒大全
崩溃和bug 当你完成应用开发并准备发布时应该将App提交审核.在提交审核前,要确保已经在设备上对应用程序进行了彻底的测试,修复了所有的bug. 断掉的链接 应用程序中所有的链接必须是功能性的.对于所 ...