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源码解析 前面 ...
随机推荐
- 【Hyeri】上传本地代码到Github
个人摸索出的上传本地代码到Github的办法 首先配置SSH Key(首次需要配置) 1.设置本地ssh key,打开git bash,输入命令: ssh -keygen -t rsa -C &quo ...
- JavaScript学习系列博客_24_JavaScript 原型对象
原型(prototype) - 创建一个函数(所有函数)以后,解析器都会默认在函数中添加一个属性prototype prototype属性指向的是一个对象,这个对象我们称为原型对象. 创建一个函数My ...
- LINUX进程ps -ef和ps -aux的区别及格式详解
Linux下显示系统进程的命令ps,最常用的有ps -ef 和ps aux.这两个到底有什么区别呢?两者没太大差别,讨论这个问题,要追溯到Unix系统中的两种风格,System V风格和BSD 风格, ...
- springSecurity初识-练气初期
1.写在前面 Spring Security是一个框架,提供针对常见攻击的身份验证,授权和保护.通过对命令式和反应式应用程序的一流支持,它是保护基于Spring的应用程序的事实标准. Spring S ...
- Spring官方宣布:新的Spring OAuth2.0授权服务器已经来了
1. 前言 记不记得之前发过一篇文章Spring 官方发起Spring Authorization Server 项目.该项目是由Spring Security主导的一个社区驱动的.独立的孵化项目.由 ...
- PowerDesigner 使用笔记
1.将mysql数据结构导入到PowerDesigner https://blog.csdn.net/guochanof/article/details/81905616 2.设计数据库过程 http ...
- Spring编程式注解使用不当导致其他事务无法正常提交
1.事故背景 原本在使用的是注解式事务,后面因为需要在事务中增加异步推送机制,所以需要将推送机制放到事务之外,修改后发现系统经常出现事务长时间无法提交导致回滚. 2.排查流程 (1)一开始重启应用是能 ...
- Http请求-okhttp3基本用法
简介 HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽.OkHttp是一个高效的HTTP客户端,它有以下默认特性: 支持HTTP/2,允许所有同一个主 ...
- vue + vant 上传图片之压缩图片
<van-uploader v-model="fileList" multiple :after-read="afterRead" :max-count= ...
- oeasy教您玩转linux010106这儿都有啥 ls
回忆上次内容 上个实验我们查询了 ls 的手册: man ls