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

一、Thread

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


  1. //空构造创建一个线程
  2. Thread()
  3. //传入Runnable对象创建一个线程
  4. Thread(Runnable target)
  5. //传入Runnable对象和线程名创建一个线程
  6. Thread(Runnable target, String name)
  7. //传入线程名创建一个线程
  8. Thread(String name)
  9. //传入线程组和Runnable对象创建一个线程
  10. Thread(ThreadGroup group, Runnable target)
  11. //传入线程组、Runnable对象和线程名创建一个线程
  12. Thread(ThreadGroup group, Runnable target, String name)
  13. //传入线程组、Runnable对象、线程名和栈大小创建一个线程
  14. Thread(ThreadGroup group, Runnable target, String name, long stackSize)
  15. //传入线程组和线程名创建一个线程
  16. Thread(ThreadGroup group, String name)

(2)常用构造

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

  1. Thread(Runnable target) 

  1. public Thread(Runnable target) {
  2. init(null, target, "Thread-" + nextThreadNum(), 0);
  3. }
  4. private static int threadInitNumber;
  5. private static synchronized int nextThreadNum() {
  6. return threadInitNumber++;
  7. }

(3)线程的初始化方法

  1. /**
  2. * @param g 线程组,每个线程都会属于一个线程组
  3. * @param target 线程会执行Runnable对象的run方法
  4. * @param name 线程名
  5. * @param stackSize 栈深度,默认是0
  6. * @param acc 获取上下文,其实是获取classloader
  7. * @param inheritThreadLocals 是否从继承的本地线程的值用于构造线程
  8. */
  9. private void init(ThreadGroup g, Runnable target, String name,
  10. long stackSize, AccessControlContext acc,
  11. boolean inheritThreadLocals) {
  12. if (name == null) {
  13. throw new NullPointerException("name cannot be null");
  14. }
  15. this.name = name;
  16. //父线程,比如在Main方法中启动一个线程,就是Main线程
  17. Thread parent = currentThread();
  18. g.addUnstarted();
  19. this.group = g;
  20. //是否为守护线程,线程分为用户线程和守护线程,只要还有一个用户
  21. //线程在跑,守护线程就不会挂掉。如果没有用户线程了,守护线程
  22. //会和JVM一起退出
  23. this.daemon = parent.isDaemon();
  24. //线程优先级
  25. this.priority = parent.getPriority();
  26. //获取上下文
  27. if (security == null || isCCLOverridden(parent.getClass()))
  28. this.contextClassLoader = parent.getContextClassLoader();
  29. else
  30. this.contextClassLoader = parent.contextClassLoader;
  31. this.inheritedAccessControlContext =
  32. acc != null ? acc : AccessController.getContext();
  33. this.target = target;
  34. setPriority(priority);
  35. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  36. this.inheritableThreadLocals =
  37. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  38. //设置线程的栈深度
  39. this.stackSize = stackSize;
  40. /* Set thread ID */
  41. tid = nextThreadID();
  42. }

(4)线程的start方法

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

  1. //threadStatus是用volatile修饰的,保证内存可见性
  2. private volatile int threadStatus = 0;
  3. public synchronized void start() {
  4. //threadStatus是线程状态,一开始是0,启动一次之后就是其他值
  5. if (threadStatus != 0)
  6. throw new IllegalThreadStateException();
  7. group.add(this);
  8. boolean started = false;
  9. try {
  10. //调用本地方法,启动线程
  11. start0();
  12. started = true;
  13. } finally {
  14. try {
  15. if (!started) {
  16. group.threadStartFailed(this);
  17. }
  18. } catch (Throwable ignore) {
  19. /* do nothing. If start0 threw a Throwable then
  20. it will be passed up the call stack */
  21. }
  22. }
  23. }

启动两次的案例:

第一次调用start方法:

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

(5)activeCount()方法


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

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

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

(6)interrupt、interrupted、isInterruped方法

  1. //interrupt仅仅是将线程的标记为中断,并不会真的中断线程
  2. public void interrupt() {
  3. if (this != Thread.currentThread())
  4. checkAccess();
  5. synchronized (blockerLock) {
  6. Interruptible b = blocker;
  7. if (b != null) {
  8. interrupt0(); // Just to set the interrupt flag
  9. b.interrupt(this);
  10. return;
  11. }
  12. }
  13. interrupt0();
  14. }
  1. //而isInterrupted是判断线程是否已被中断
  2. public boolean isInterrupted() {
  3. return isInterrupted(false);
  4. }
  5. private native boolean isInterrupted(boolean ClearInterrupted);

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


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

下面是中断线程的例子:


  1. Thread a = new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. if (Thread.currentThread().isInterrupted()) {
  5. System.out.println("【"+Thread.currentThread().getName()+"】当前线程数量"+Thread.activeCount());
  6. System.out.println("线程被中断");
  7. return;
  8. }
  9. while (true) {
  10. }
  11. }
  12. },"a");
  13. a.start();
  14. a.interrupt();
  15. try {
  16. Thread.sleep(3000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println("【Main】当前线程数量"+Thread.activeCount());

运行结果:

(7)线程状态

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

二、ThreadLocal

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

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

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

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

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

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

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

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

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


  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

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


  1. ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  2. table = new Entry[INITIAL_CAPACITY];
  3. int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  4. table[i] = new Entry(firstKey, firstValue);
  5. size = 1;
  6. setThreshold(INITIAL_CAPACITY);
  7. }
  1. private static final int INITIAL_CAPACITY = 16;
  2. //初始值是10
  3. private void setThreshold(int len) {
  4. threshold = len * 2 / 3;
  5. }
  6. private void set(ThreadLocal<?> key, Object value) {
  7. Entry[] tab = table;
  8. int len = tab.length;
  9. //计算index
  10. int i = key.threadLocalHashCode & (len-1);
  11. //从下标为i的一直往下,如果不为空,则替换值
  12. for (Entry e = tab[i];
  13. e != null;
  14. e = tab[i = nextIndex(i, len)]) {
  15. ThreadLocal<?> k = e.get();
  16. if (k == key) {
  17. e.value = value;
  18. return;
  19. }
  20. if (k == null) {
  21. replaceStaleEntry(key, value, i);
  22. return;
  23. }
  24. }
  25. //设置值
  26. tab[i] = new Entry(key, value);
  27. int sz = ++size;
  28. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  29. //重新hash,里面有扩容的方法
  30. rehash();
  31. }
  32. private void rehash() {
  33. expungeStaleEntries();
  34. // 当元素数量大于(threshold - threshold / 4),就会进行扩容
  35. //,当threadhold为10的时候就是8,
  36. if (size >= threshold - threshold / 4)
  37. resize();
  38. }
  39. //扩容
  40. private void resize() {
  41. Entry[] oldTab = table;
  42. int oldLen = oldTab.length;
  43. //两倍扩容
  44. int newLen = oldLen * 2;
  45. Entry[] newTab = new Entry[newLen];
  46. int count = 0;
  47. for (int j = 0; j < oldLen; ++j) {
  48. Entry e = oldTab[j];
  49. if (e != null) {
  50. ThreadLocal<?> k = e.get();
  51. if (k == null) {
  52. e.value = null; // Help the GC
  53. } else {
  54. int h = k.threadLocalHashCode & (newLen - 1);
  55. while (newTab[h] != null)
  56. h = nextIndex(h, newLen);
  57. newTab[h] = e;
  58. count++;
  59. }
  60. }
  61. }
  62. //重新设置threadhold的值
  63. setThreshold(newLen);
  64. size = count;
  65. table = newTab;
  66. }

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

  1. 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在别的地方引用的话,就不会被回收,这样会造成内存溢出的情况。

例如:

  1. jvm变量设置-Xmx10m -Xms10m
  1. valuelist中保持引用,内存溢出

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

(2)get方法


  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. //存在则返回
  7. if (e != null) {
  8. @SuppressWarnings("unchecked")
  9. T result = (T)e.value;
  10. return result;
  11. }
  12. }
  13. //不存在,则先将值设为NULL,然后返回
  14. return setInitialValue();
  15. }
  16. private T setInitialValue() {
  17. T value = initialValue();
  18. Thread t = Thread.currentThread();
  19. ThreadLocalMap map = getMap(t);
  20. if (map != null)
  21. map.set(this, value);
  22. else
  23. createMap(t, value);
  24. return value;
  25. }

(3)remove方法

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null)
  4. m.remove(this);
  5. }
  6. private void remove(ThreadLocal<?> key) {
  7. Entry[] tab = table;
  8. int len = tab.length;
  9. int i = key.threadLocalHashCode & (len-1);
  10. for (Entry e = tab[i];
  11. e != null;
  12. e = tab[i = nextIndex(i, len)]) {
  13. if (e.get() == key) {
  14. //清除
  15. e.clear();
  16. expungeStaleEntry(i);
  17. return;
  18. }
  19. }
  20. }
  21. //将entry的引用置为null
  22. public void clear() {
  23. this.referent = null;
  24. }

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

我是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. day35:线程队列&进程池和线程池&回调函数&协程

    目录 1.线程队列 2.进程池和线程池 3.回调函数 4.协程:线程的具体实现 5.利用协程爬取数据 线程队列 1.线程队列的基本方法 put 存 get 取 put_nowait 存,超出了队列长度 ...

  2. 基于Java的二叉树的三种遍历方式的递归与非递归实现

    二叉树的遍历方式包括前序遍历.中序遍历和后序遍历,其实现方式包括递归实现和非递归实现. 前序遍历:根节点 | 左子树 | 右子树 中序遍历:左子树 | 根节点 | 右子树 后序遍历:左子树 | 右子树 ...

  3. python3 raw 数据转换为jpg

    python3 raw 数据转换为jpg 我们大家都知道,sensor 直接出来的裸数据为raw 数据,没有经过编解码,压缩. 我们需要将raw数据转换为其他格式比如jpg,png,bmp 人眼才能看 ...

  4. 重写简易的confirm函数

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

  5. 一文带你深扒ClassLoader内核,揭开它的神秘面纱!

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」. 如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力! 前言 Clas ...

  6. Lua索引、伪索引、引用

    索引:堆栈的索引 伪索引:一个类似于索引,但是有着特殊存储的索引,使用方式和索引一样,看上去像在操作堆栈 引用:LUA_REGISTRYINDEX伪索引下的表的整数键

  7. Java的字符串操作

    目录 Java的字符串操作 一.不同字符串操作的对比 1.1 C++中const修饰指针 const在星号的左边,是被指向的常量不可变 const在星号的右边,是指针的指向不可变 二. Java字符串 ...

  8. 使用IDEA写Python之pytest环境搭建及第一个程序编写

    一.准备篇 Python环境:3.8.3 开发工具:IDEA,对你没有看错 二.IDEA下安装开发环境 1. python的下载 https://www.python.org/downloads/ P ...

  9. JavaWeb三大器(过滤器、拦截器、监听器)概念梳理

    最近工作碰到了一个问题:项目A需要收集项目B中的用户活跃数信息,最后通过HttpSessionAttributeListener实现.在开发过程中,网上查找了过滤器.拦截器.监听器的帖子,这里对自己收 ...

  10. sql server 2008 merge matched判定条件

    SQL Server 2008 开始支持 MERGE语句    -- 源表 CREATE TABLE test_from (id INT, val VARCHAR(20));   -- 目标表 CRE ...