线程封闭之栈封闭和ThreadLocal

瘢壳枚举
• 阅读 1779

线程封闭

  在多线程的环境中,我们经常使用锁来保证线程的安全,但是对于每个线程都要用的资源使用锁的话那么程序执行的效率就会受到影响,这个时候可以把这些资源变成线程封闭的形式。

 1、栈封闭

  所谓的栈封闭其实就是使用局部变量存放资源,我们知道局部变量在内存中是存放在虚拟机栈中,而栈又是每个线程私有独立的,所以这样可以保证线程的安全。

 2、ThreadLocal

  我们先看ThreadLocal和线程Thread的关系图。

  线程封闭之栈封闭和ThreadLocal线程封闭之栈封闭和ThreadLocal

   再看下ThreadLocal的操作,以get为例

public T get() {        // 当前线程        Thread t = Thread.currentThread();   // 拿到当前线程的threadLocalMap,即上图中的map引用        ThreadLocalMap map = getMap(t);        if (map != null) {            // 拿到当前ThreadLocal为Key对应的Entry,里面做了防止内存泄漏的处理            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        // 如果为null设置默认值        return setInitialValue();    }

  如上面get方法的源码所示,在调用threadLocal.get()方法的时候,threadLocal拿到当前线程中ThreadLocalMap中以threadLocal自身为key对应的entry,在这个getEntry方法中里面做了内存泄漏的处理,大概处理逻辑就是如果threadLocal对应的Entry为null的话,让这个entry的value为null并且map中threadLocal对应下标置null,如果不为null的话返回,否则的话则调用默认值方法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;    }  // 默认null实现 protected T initialValue() {        return null;  }

  setInitialValue()方法逻辑比较简单,这里不多赘述,值得注意的是里面调用的initialValue(),并没有任何的实现,所以我们使用threadLocal的时候一般都会选择重写实现这个方法。

 // 这里main方法测试,所以用static修饰,会延长threadLocal的生命周期,有内存泄漏的风险,一般作为成员变量就足够了 public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(){        @Override        protected String initialValue() {            return "init string from initialValue method";        }    };    public static void main(String[] args) throws InterruptedException {        // 未放入value直接调用get        System.err.println("invoke get before any set:" + threadLocal.get());        threadLocal.set("test");        System.err.println("before thread start : " + threadLocal.get());        new Thread(() -> {            // 对相同的threadLocal对象放入值            threadLocal.set("test in thread");            System.err.println("In thread[" + Thread.currentThread().getName() + "] threadLocal value : " + threadLocal.get());        }).start();        TimeUnit.SECONDS.sleep(1);        // 证明threadLocal中的value不在线程中共享        System.err.println("after thread value : " + threadLocal.get());    }result:  
  结合这个小程序和上面的图就可以对threadLocal有一个大概的理解了。其他的方法如set、remove等方法都大同小异,可以结合图片去看源码,这里不再赘述。

  关于内存泄漏的问题

    1、在threadLocal的get、set、remove方法中,其对本身可能发生的内存泄漏都做了处理,逻辑上面也提到如果对应entry为null,将其value置null,将map中对应下标引用置null。

    2、而对于threadLocal中这个对象的泄漏来说,则是采用弱引用的方式来实现,在上面的图中,我用虚线来表示弱引用,弱引用的意思是在JVM进行垃圾回收的时候这个引用会被回收(无论内存足够与否);试想一下,如果使用强引用并且栈中的引用消失了,那么在线程结束之前这个threadLocal对象不会被回收且无法访问,也就是造成内存泄漏。

 3、Java四种引用的简要概述

  上面在ThreadLocal提到了弱引用,这里顺便简单的说下Java中的四种引用。

  1. 强引用:指new出来的对象,一般没有特别申明的对象都是强引用。这种对象只有在GCroots找不到它的时候才会被回收。
  2. 软引用(SoftReference的子类):GC后内存不足的情况将只有这种引用的对象回收。
  3. 弱引用(WeakReference的子类):GC时回收只有此引用的对象(无论内存是否不足)。
  4. 虚引用(PhantomReference子类):没有特别的功能,类似一个追踪符,配合引用队列来记录对象何时被回收。(实际上这四种引用都可以配合引用队列使用,只要在构造方法中传入需要关联的引用队列就行,在对象调用finalize方法的时候会被写入到队列当中)
若有不正之处,望指出!
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java ThreadLocal
ThreadLocal是什么定义:提供线程局部变量;一个线程局部变量在多个线程中,分别有独立的值(副本)特点:简单(开箱即用)、快速(无额外开销)、安全(线程安全)场景:多线程场景(资源持有、线程一致性、并发计算、线程安全等场景)ThreadLocal基本API 构
灯灯灯灯 灯灯灯灯
4年前
一次性带你了解清楚Java内存模型!
Java内存模型咳咳咳,能看完的都是人上人。。。。Java虚拟机内部使用JMM(Java内存模型)将内存划分为两个逻辑单元,线程栈(或者叫本地内存)和堆。每一个线程都有属于自己的线程栈,在线程栈中会保存局部变量(也叫做本地变量)、方法中定义的参数和异常处理器的参数(catch中的参数);这些参数和变量都属于线程局部操作,会被隔离,所以不受内存模
Wesley13 Wesley13
3年前
java并发程序和共享对象实用策略
java并发程序和共享对象实用策略在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:1.线程封闭2.只读共享。共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象3.线程安全共享。线程安全地对象在器内部实现同步。4.保护对象。被保护的对象只能通过持有特定的锁
Wesley13 Wesley13
3年前
java多线程(四)之同步机制
1.同步的前提  多个线程  多个线程使用的是同一个锁2.同步的好处  同步的出现解决了多线程的安全问题3.同步的弊端  当线程较多时,因为每个线程都会去判断同步上的锁,这样是很耗费资源的,会降低程序的运行效率.4.同步方法:  1.就是将同步关键字,synchronized加到方法上,此时的锁对象是this  
Tankard825 Tankard825
4年前
最常见的java面试题汇总
1.什么是线程局部变量?(答案)线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如web服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何
Wesley13 Wesley13
3年前
Java多线程优化
\以下文章来源于51CTO技术栈 ,作者崔皓今天,我们从Java内部锁优化,代码中的锁优化,以及线程池优化几个方面展开讨论。Java 内部锁优化当使用Java多线程访问共享资源的时候,会出现竞态的现象。即随着时间的变化,多线程“写”共享资源的最终结果会有所不同。为了解决这个问题,让多线程“写”资源的时候有先后顺序,引入
Wesley13 Wesley13
3年前
JAVA基础系列:ThreadLocal
1. 思路1.什么是ThreadLocal?ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。2.它大致的实现
Easter79 Easter79
3年前
ThreadLocal 详解
概念ThreadLocal用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说ThreadLocal可以为每个线程创建一个【单独的变量副本】,相当于线程的privatestatic类型变量。使用示例publicclassThreadLocalTest{
Wesley13 Wesley13
3年前
Java多线程——线程封闭
线程封闭:当访问共享的可变数据时,通常需要同步。一种避免同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread confinement)  线程封闭技术一个常见的应用就是JDBC的Connection对象,JDBC规范并没有要求Connection对象必须是线程安全的,在服务器应用程序中,线程从连接
Stella981 Stella981
3年前
JVM学习第一天
程序计数器当前线程所执行的字节码的行号指示器每个线程都有自己私有的计数器native方法,计数器值为空该内存区域没有规定任何的OutOfMemoryError情况虚拟机栈Java方法执行的内存模型,用于存储局部变量标、操作数栈、动态链接、方法出口等信息虚拟机栈也是线程私有局部变量表所需的内存控件在