ThreadLocal作用原理与内存泄露示例解析

目录
  • ThreadLocal作用
  • 简单例子
  • 局部变量、成员变量 、 ThreadLocal、静态变量
  • 共享 or 隔离
  • 原理
  • 源码分析
    • TheadLocal
    • TheadLocalMap
  • ThreadLocal与内存泄漏
  • 小结

ThreadLocal作用

对于Android程序员来说,很多人都是在学习消息机制时候了解到ThreadLocal这个东西的。那它有什么作用呢?官方文档大致是这么描述的:

  • ThreadLocal提供了线程局部变量
  • 每个线程都拥有自己的变量副本,可以通过ThreadLocal的set或者get方法去设置或者获取当前线程的变量,变量的初始化也是线程独立的(需要实现initialValue方法)
  • 一般而言ThreadLocal实例在类中被private static修饰
  • 当线程活着并且ThreadLocal实例能够访问到时,每个线程都会持有一个到它的变量的引用
  • 当一个线程死亡后,所有ThreadLocal实例给它提供的变量都会被gc回收(除非有其它的引用指向这些变量) 上述中“变量”是指ThreadLocal的get方法获取的值

简单例子

先来看一个简单的使用例子吧:

public class ThreadId {
    private static final AtomicInteger nextId = new AtomicInteger(0);
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.get();
        }
    };
    public static int get() {
        return threadId.get();
    }
}

这也是官方文档上的例子,非常简单,就是通过在不同线程调用ThredId.get()可以获取唯一的线程Id。如果在调用ThreadLocal的get方法之前没有主动调用过set方法设置值的话,就会返回initialValue方法的返回值,并把这个值存储为当前线程的变量。

ThreadLocal到底是用来解决什么问题,适用什么场景呢,例子是看懂了,但好像还是没什么体会?ThreadLocal既然是提供变量的,我们不妨把我们见过的变量类型拿出来,做个对比

局部变量、成员变量 、 ThreadLocal、静态变量

变量类型 作用域 生命周期 线程共享性 作用
局部变量 方法(代码块)内部,其他方法(代码块)不能访问 方法(代码块)开始到结束 只存在于每个线程的工作内存,不能在线程中共享 解决变量在方法(代码块)内部的代码行之间的共享
成员变量 实例内 和实例相同 可在线程间共享 解决变量在实例方法之间的共享,否则方法之间只能靠参数传递变量
静态变量 类内部 和类的生命周期相同 可在多个线程间共享 解决变量在多个实例之间的共享
ThreadLocal存储的变量 整个线程 一般而言与线程的生命周期相同 不再多线程间共享 解决变量在单个线程中的共享问题,线程中处处可访问

ThreadLocal存储的变量本质上间接算是Thread的成员变量,ThreadLocal只是提供了一种对开发者透明的可以为每个线程存储同一维度成员变量的方式。

共享 or 隔离

网上有很多人持有如下的看法: ThreadLocal为解决多线程程序的并发问题提供了一种新思路或者ThreadLocal是为了解决多线程访问资源时的共享问题。 个人认为这些都是错误的,ThreadLocal保存的变量是线程隔离的,与资源共享没有任何关系,也没有解决什么并发问题,这一点看了ThreadLocal的原理就会更加清楚。就好比上面的例子,每个线程应该有一个线程Id,这并不是什么并发问题啊。

同时他们会拿ThreadLocal与sychronized做对比,我们要清楚它们根本不是为了解决同一类问题设计的。sychronized是在牵涉到共享变量时候,要做到线程间的同步,保证并发中的原子性与内存可见性,典型的特征是多个线程会访问相同的变量。而ThreadLocal根本不是解决线程同步问题的,它的场景是A线程保存的变量只有A线程需要访问,而其它的线程并不需要访问,其他线程也只访问自己保存的变量。

原理

我们来一个开放性的问题,假如现在要给每个线程增加一个线程Id,并且Java的Thread类你能随便修改,你要怎么操作?非常简单吧,代码大概是这样

public class Thread{
      private int id;
      public void setId(int id){
          this.id=id;
      }
}

那好,现在题目变了,我们现在还得为每个线程保存一个Looper对象,那怎么办呢?再加一个Looper的字段不就好了,显然这种做法肯定是不具有扩展性的。那我们用一个容器类不就好了,很自然地就会想到Map,像下面这样

public class Thread{
      private Map<String,Object> map;
     public Map<String,Object> getMap(){
         if(map==null)
            map=new HashMap<>();
         return map;
     }
}

然后我们在代码里就可以通过如下代码来给Thread设置“成员变量”了

   Thread.currentThread().getMap().put("id",id);
   Thread.currentThread().getMap().put("looper",looper);

然后可以在该线程执行的任意地方,这样访问:

  Looper looper=(Looper) Thread.currentThread().getMap().get("looper");

看上去还不错,但是还是有些问题:

  • 保存和获取变量都要用到字符换key
  • 因为map中要保存各种值,因此泛型只得用Object,这样获取时候就需要强制转换(可用泛型方法解)
  • 当该变量没有作用时候,此时线程还没有执行完,需要手动设置该变量为空,否则会造成内存泄漏

为了不通过字符串访问,同时省去强制转换,我们封装一个类,就叫ThreadLocal吧,伪代码如下:

  public class ThreadLocal<T> {
    public void set(T value) {
        Thread t = Thread.currentThread();
         Map map = t.getMap();
        if (map != null)
           //以自己为键
            map.put(this, value);
        else
            createMap(t, value);
    }
    public T get() {
        Thread t = Thread.currentThread();
        Map<ThreadLocal<?>,T> map = t.getMap();
        if (map != null) {
            T e = map.get(this);
            return e;
        }
        return setInitialValue();
    }
}

没错,以上基本上就是ThreadLocal的整体设计了,只是线程中存储数据的Map是特意实现的ThreadLocal.ThreadLocalMap。

ThredLocal本身并不存储变量,只是向每个线程的threadLocals中存储键值对。ThreadLocal横跨线程,提供一种类似切面的概念,这种切面是作用在线程上的。

我们对ThreadLocal已经有一个整体的认识了,接下来我们大致看一下源码

源码分析

TheadLocal

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

set方法通过Thread.currentThread方法获取当前线程,然后调用getMap方法获取线程的threadLocals字段,并往ThreadLocalMap中放入键值对,其中键为ThreadLocal实例自己。

 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
   }

接着看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;
            }
        }
        return setInitialValue();
    }

很清晰,其中值得注意的是最后一行的setInitialValue方法,这个方法在我们没有调用过set方法时候调用。

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;
    }

setInitialValue方法会获取initialValue的返回值并把它放进当前线程的threadLocals中。默认情况下initialValue返回null,我们可以实现这个方法来对变量进行初始化,就像上面TheadId的例子一样。

remove方法,从当前线程的ThreadLocalMap中移除元素。

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

TheadLocalMap

看ThreadLocalMap的代码我们主要是关注以下两个方面:

  • 散列表的一般设计问题。包括散列函数,散列冲突问题解决,负载因子,再散列等。
  • 内存泄漏的相关处理。一般而言ThreadLocal 引用使用private static修饰,但是假设某种情况下我们真的不再需要使用它了,手动把引用置空。上面我们知道TreadLocal本身作为键存储在TheadLocalMap中,而ThreadLocalMap又被Thread引用,那线程没结束的情况下ThreadLocal能被回收吗?

散列函数 先来理一下散列函数吧,我们在之后的代码中会看到ThreadLocalMap通过 int i = key.threadLocalHashCode & (len-1);决定元素的位置,其中表大小len为2的幂,因此这里的&操作相当于取模。另外我们关注的是threadLocalHashCode的取值。

  private final int threadLocalHashCode = nextHashCode();
 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
  private static AtomicInteger nextHashCode =
        new AtomicInteger();
   private static final int HASH_INCREMENT = 0x61c88647;

这里很有意思,每个ThreadLocal实例的threadLocalHashCode是在之前ThreadLocal实例的threadLocalHashCode上加 0x61c88647,为什么偏偏要加这么个数呢? 这个魔数的选取与斐波那契散列有关以及黄金分割法有关,具体不是很清楚。它的作用是这样产生的值与2的幂取模后能在散列表中均匀分布,即便扩容也是如此。看下面一段代码:

  public class MagicHashCode {
      //ThreadLocal中定义的魔数
     private static final int HASH_INCREMENT = 0x61c88647;
     public static void main(String[] args) {
         hashCode(16);//初始化16
         hashCode(32);//2倍扩容
         hashCode(64);
     }
    private static void hashCode(int length){
        int hashCode = 0;
         for(int i=0;i<length;i++){
            hashCode = i*HASH_INCREMENT+HASH_INCREMENT;
             System.out.print(hashCode & (length-1));//求取模后的下标
             System.out.print(" ");
         }
         System.out.println();
     }
 }

输出结果为:

7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0   //容量为16时
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0  //容量为32时
7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 16 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0  //容量为64时

因为ThreadLocalMap使用线性探测法解决冲突(下文会看到),均匀分布的好处在于发生了冲突也能很快找到空的slot,提高效率。

瞄一眼成员变量:

       /**
         * 初始容量,必须是2的幂。这样的话,方便把取模运算转化为与运算,
         * 效率高
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * 容纳Entry元素,长度必须是2的幂
         */
        private Entry[] table;
        /**
         * table中的元素个数.
         */
        private int size = 0;
        /**
         * table里的元素达到这个值就需要扩容了
         * 其实是有个装载因子的概念的
         */
        private int threshold; // Default to 0

构造函数:

  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);
  }

firstKey和firstValue就是Map存放的第一个键值对喽。其中firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)很关键,就是当容量为2的幂时候,这相当于一个取模操作。然后把Entry存储到数组的第i个位置,设置扩容的阈值。

private void setThreshold(int len) {
          threshold = len * 2 / 3;
 }

这说明当数组里的元素容量达到2/3时候就要扩容,也就是装载因子是2/3。 接下来我们来看下Entry

 static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

就这么点东西,这个Entry只是与HashMap不同,只是个普通的键值对,没有链表结构相关的东西。

另外Entry只持有对键,也就是ThreadLocal的弱引用,那么我们上面的第二个问题算是有答案了。当没有其他强引用指向ThreadLocal的时候,它其实是会被回收的。

但是这有引出了另外一个问题,那Entry呢?当键都为空的时候这个Entry也是没有什么作用啊,也应该被回收啊。不慌,我们接着往下看。

set方法:

 private void set(ThreadLocal<?> key, Object value) {
            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)]) {
                ThreadLocal<?> k = e.get();
               //判断键是否相等,相等的话只要更新值就好了
                if (k == key) {
                    e.value = value;
                    return;
                }
                if (k == null) {
                   //该Entry对应的ThreadLocal已经被回收,执行replaceStaleEntry并返回
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //进行启发式清理,如果没有清理任何元素并且表的大小超过了阈值,需要扩容并重哈希
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

我们发现如果发生冲突的话,整体逻辑会一直调用nextIndex方法去探测下一个位置,直到找到没有元素的位置,逻辑上整个表是一个环形。下面是nextIndex的代码,就是加1而已。

private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
}

线性探测的过程中,有一种情况是需要清理对应Entry的,也就是Entry的key为null,我们上面讨论过这种情况下的Entry是无意义的。

因此调用 replaceStaleEntry(key, value, i);在看replaceStaleEntry(key, value, i)我们先明确几个问题。

采用线性探测发解决冲突,在插入过程中产生冲突的元素之前一定是没有空的slot的。这样在也确保在查找过程,查找到空的slot就可以停止啦。

但是假如我们删除了一个元素,就会破坏这种情况,这时需要对表中删除的元素后面的元素进行再散列,以便填上空隙。

空slot:即该位置没有元素

无效slot:该位置有元素,但key为null

replaceStaleEntry除了将value放入合适的位置之外,还会在前后连个空的slot之间做一次清理expungeStaleEntry,清理掉无效slot。

private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;
    // 向前扫描到一个空的slot为止,找到离这个空slot最近的无效slot,记录为slotToExpunge
    int slotToExpunge = staleSlot;
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len)) {
        if (e.get() == null) {
            slotToExpunge = i;
        }
    }
    // 向后遍历table
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 找到了key,将其与无效slot交换
        if (k == key) {
            // 更新对应slot的value值
            e.value = value;
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;
            //如果之前还没有探测到过其他无效的slot
            if (slotToExpunge == staleSlot) {
                slotToExpunge = i;
            }
            // 从slotToExpunge开始做一次连续段的清理,再做一次启发式清理
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
        // 如果当前的slot已经无效,并且向前扫描过程中没有无效slot,则更新slotToExpunge为当前位置
        if (k == null && slotToExpunge == staleSlot) {
            slotToExpunge = i;
        }
    }
    // 如果key之前在table中不存在,则放在staleSlot位置
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);
    // 在探测过程中如果发现任何其他无效slot,连续段清理后做启发式清理
    if (slotToExpunge != staleSlot) {
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }
}

expungeStaleEntry主要是清除连续段之前无效的slot,然后对元素进行再散列。返回下一个空的slot位置。

 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            // 删除 staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                   //对元素进行再散列
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

启发式地清理: i对应是非无效slot(slot为空或者有效) n是用于控制控制扫描次数 正常情况下如果log n次扫描没有发现无效slot,函数就结束了。 但是如果发现了无效的slot,将n置为table的长度len,做一次连续段的清理,再从下一个空的slot开始继续扫描。

这个函数有两处地方会被调用,一处是插入的时候可能会被调用,另外个是在替换无效slot的时候可能会被调用, 区别是前者传入的n为实际元素个数,后者为table的总容量。

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        // i在任何情况下自己都不会是一个无效slot,所以从下一个开始判断
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            // 扩大扫描控制因子
            n = len;
            removed = true;
            // 清理一个连续段
            i = expungeStaleEntry(i);
        }
    } while ((n >>>= 1) != 0);
    return removed;
}

接着看set函数,如果循环过程中没有返回,找到合适的位置,插入元素,表的size增加1。这个时候会做一次启发式清理,如果启发式清理没有清理掉任何无效元素,判断清理前表的大小大于阈值threshold的话,正常就要进行扩容了,但是表中可能存在无效元素,先把它们清除掉,然后再判断。

private void rehash() {
    // 全量清理
    expungeStaleEntries();
    //因为做了一次清理,所以size可能会变小,这里的实现是调低阈值来判断是否需要扩容。 threshold默认为len*2/3,所以这里的threshold - threshold / 4相当于len/2。
    if (size >= threshold - threshold / 4) {
        resize();
    }
}

作用即清除所有无效slot

private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null) {
            expungeStaleEntry(j);
        }
    }
}

保证table的容量len为2的幂,扩容时候要扩大2倍

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;
            } else {
                // 扩容后要重新放置元素
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null) {
                    h = nextIndex(h, newLen);
                }
                newTab[h] = e;
                count++;
            }
        }
    }
    setThreshold(newLen);
    size = count;
    table = newTab;
}

get方法:

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // 对应的entry存在且key未被回收
    if (e != null && e.get() == key) {
        return e;
    } else {
        // 继续往后查找
        return getEntryAfterMiss(key, i, e);
    }
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // 不断向后探测直到遇到空entry
    while (e != null) {
        ThreadLocal<?> k = e.get();
        // 找到
        if (k == key) {
            return e;
        }
        if (k == null) {
            // 该entry对应的ThreadLocal实例已经被回收,调用expungeStaleEntry来清理无效的entry
            expungeStaleEntry(i);
        } else {
            // 下一个位置
            i = nextIndex(i, len);
        }
        e = tab[i];
    }
    return null;
}

remove方法,比较简单,在table中找key,如果找到了断开弱引用,做一次连续段清理。

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;
        }
    }
}

ThreadLocal与内存泄漏

从上文我们知道当调用ThreadLocalMap的set或者getEntry方法时候,有很大概率会去自动清除掉key为null的Entry,这样就可以断开value的强引用,使对象被回收。

但是如果如果我们之后再也没有在该线程操作过任何ThreadLocal实例的set或者get方法,那么就只能等线程死亡才能回收无效value。

因此当我们不需要用ThreadLocal的变量时候,显示调用ThreadLocal的remove方法是一种好的习惯。

小结

  • ThredLocal为每个线程保存一个自己的变量,但其实ThreadLocal本身并不存储变量,变量存储在线程自己的实例变量ThreadLocal.ThreadLocalMap threadLocals
  • ThreadLocal的设计并不是为了解决并发问题,而是解决一个变量在线程内部的共享问题,在线程内部处处可以访问
  • 因为每个线程都只会访问自己ThreadLocalMap 保存的变量,所以不存在线程安全问题

以上就是ThreadLocal作用原理与内存泄露示例解析的详细内容,更多关于ThreadLocal内存泄露的资料请关注我们其它相关文章!

(0)

相关推荐

  • java并发高的情况下用ThreadLocalRandom来生成随机数

    目录 一:简述 二:Random的性能差在哪里 三:ThreadLocalRandom的简单使用 四:为什么ThreadLocalRandom能在保证线程安全的情况下还能有不错的性能 一:简述 如果我们想要生成一个随机数,通常会使用Random类.但是在并发情况下Random生成随机数的性能并不是很理想,今天给大家介绍一下JUC包中的用于生成随机数的类--ThreadLocalRandom.(本文基于JDK1.8) 二:Random的性能差在哪里 Random随机数生成是和种子seed有关,而为

  • 基于ThreadLocal 的用法及内存泄露(内存溢出)

    目录 使用 构造方法 静态方法 公共方法 内存泄露 解决方法 为什么要将ThreadLocal 定义成 static 变量 对ThreadLocal内存泄漏引起的思考 概述 使用场景样例代码 ThreadLocal使用源码 思考问题 ThreadLocal解读 ThreadLocal 看名字 就可以看出一点头绪来,线程本地. 来看一下java对他的描述: 该类提供线程本地变量.这些变量与它们的正常对应变量的不同之处在于,每个线程(通过ThreadLocal的 get 或 set方法)访问自己的.

  • 实例详解Java中ThreadLocal内存泄露

    案例与分析 问题背景 在 Tomcat 中,下面的代码都在 webapp 内,会导致WebappClassLoader泄漏,无法被回收. public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class MyThreadLocal extends ThreadLocal<MyCount

  • 面试官:java ThreadLocal真的会造成内存泄露吗

    目录 1.ThreadLocal知识体系 2.为什么会被设计为弱引用呢? 3.大量Entry造成的内存溢出问题探讨 总结 1.ThreadLocal知识体系 本文还是不能免俗,在回答这个问题之前需要先和大家介绍一下ThreadLocal的知识,使大家对ThreadLocal有一个相对全面的认识. ThreadLocal本地线程变量,主要用于解决数据访问的竞争,通常用于多租户.全链路压测.链路跟踪中保存线程上下文环境,在一个请求流转中非常方便的获取一些关键信息,例如当前的租户信息.压测标记. Th

  • Java 超详细讲解ThreadLocal类的使用

    目录 Threadlocal有什么用: ThreadLocal使用实例 API介绍 ThreadLocal的使用 Threadlocal 的源码分析 原理 源码 内部类ThreadLocalMap ThreadLocalMap存储位置 Key的弱引用问题 java中的四种引用 总结: Threadlocal有什么用: 简单的说就是,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值).如下图: ThreadLocal使用实例 API介绍 在使

  • C#多线程之线程绑定ThreadLocal类

    在.Net 4.0的Thread里,新增了线程局部变量(ThreadLocal)类,可以很方便的实现线程专有存储. 应用场景 线程专有存储应被用于这样的多线程应用:它们经常访问那些逻辑上是全局的.而物理上是专有于每个线程的对象.首先我们看如下这样一个例子 string errorMessage; void Process() { bool ret = Run(); if (!ret && needDebug) { Console.WriteLine(errorMessage); } } b

  • SpringBoot通过ThreadLocal实现登录拦截详解流程

    目录 1 前言 2 具体类 2.1HandlerInterceptor 2.2WebMvcConfigurer 3 代码实践 1 前言 注册登录可以说是平时开发中最常见的东西了,但是一般进入到公司之后,像这样的功能早就开发完了,除非是新的项目.这两天就碰巧遇到了这样一个需求,完成pc端的注册登录功能. 实现这样的需求有很多种方式:像 1)HandlerInterceptor+WebMvcConfigurer+ThreadLocal 2)Filter过滤器 3)安全框架Shiro(轻量级框架) 4

  • ThreadLocal作用原理与内存泄露示例解析

    目录 ThreadLocal作用 简单例子 局部变量.成员变量 . ThreadLocal.静态变量 共享 or 隔离 原理 源码分析 TheadLocal TheadLocalMap ThreadLocal与内存泄漏 小结 ThreadLocal作用 对于Android程序员来说,很多人都是在学习消息机制时候了解到ThreadLocal这个东西的.那它有什么作用呢?官方文档大致是这么描述的: ThreadLocal提供了线程局部变量 每个线程都拥有自己的变量副本,可以通过ThreadLocal

  • c语言内存泄露示例解析

    正确的内存管理的重要性存在内存错误的 C 和 C++ 程序会导致各种问题.如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行:如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击.从 1988 年著名的莫里斯蠕虫 攻击到有关 Flash Player 和其他关键的零售级程序的最新安全警报都与缓冲区溢出有关:"大多数计算机安全漏洞都是缓冲区溢出",Rodney Bates 在 2004 年写道. 在可以使用 C 或 C++ 的地方,也广泛支持使用其他许多通用语言(如 Java™.

  • android的GC内存泄露问题

    1. android内存泄露概念 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的

  • 理解Java中的内存泄露及解决方法示例

    本文详细地介绍了Java内存管理的原理,以及内存泄露产生的原因,同时提供了一些列解决Java内存泄露的方案,希望对各位Java开发者有所帮助. Java内存管理机制 在C++ 语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期.从申请分配.到使用.再到最后的释放.这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的泄露. Java 语言对内存管理做了自己的优化,这就是垃圾回收机制. Java 的几乎所有内存对象都是在堆内存上分配(基本数据类型

  • 详解Java内存泄露的示例代码

    在定位JVM性能问题时可能会遇到内存泄露导致JVM OutOfMemory的情况,在使用Tomcat容器时如果设置了reloadable="true"这个参数,在频繁热部署应用时也有可能会遇到内存溢出的情况.Tomcat的热部署原理是检测到WEB-INF/classes或者WEB-INF/lib目录下的文件发生了变更后会把应用先停止然后再启动,由于Tomcat默认给每个应用分配一个WebAppClassLoader,热替换的原理就是创建一个新的ClassLoader来加载类,由于JVM

  • FreeRTOS进阶内存管理示例完全解析

    内存管理对应用程序和操作系统来说都非常重要.现在很多的程序漏洞和运行崩溃都和内存分配使用错误有关. FreeRTOS操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管理函数原型,而不关心这些内存管理函数是如何实现的.这样做大有好处,可以增加系统的灵活性:不同的应用场合可以使用不同的内存分配实现,选择对自己更有利的内存管理策略.比如对于安全型的嵌入式系统,通常不允许动态内存分配,那么可以采用非常简单的内存管理策略,一经申请的内存,甚至不允许被释放.在满足设计要求的前提下,系统越简单

  • 深入解析PHP垃圾回收机制对内存泄露的处理

    上次说到了refcount和is_ref,这里来说说内存泄露的情况 复制代码 代码如下: $a = array(1, 2, &$a);unset($a); 在老的PHP版本中,这里就会出现内存泄露,分析如下: 执行第一行,可以知道$a和$a[2]指向的zval refcount=2,is_ref=1 然后执行第二行,$a将会从符号表中被删除,同时指向的zval的refcount--,此时refcount=1,因为refcount!=0,故此zval不会被当做垃圾回收,但是此时我们却失去了$a[2

  • java内存泄漏与内存溢出关系解析

    这篇文章主要介绍了java内存泄漏与内存溢出关系解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory: 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory!

随机推荐