【九】线程副本ThreadLocal,及WeakHashMap

实现多线程安全的三种方法,

1. 使用锁机制 synchronize、lock方式:为资源加锁,可参考我前面文章。

2. 使用 java.util.concurrent 下面的类库:有JDK提供的线程安全的集合类:AtomicInteger、AtomicStampedReference、ConcurrentHashMap、CopyOnWriteArrayList、ReentrantLock,FutureTask,CountDownLatch。

3. 多实例、或者是多副本(ThreadLocal):ThreadLocal可以为每个线程的维护一个私有的本地变量。

 

这里主要讲讲ThreadLocal:

ThreadLocal解决多线程的并发问题的思路很简单:就是为每一个线程维护一个副本变量,从而让线程拥有私有的资源,就不再需要去竞争进程中的资源。每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

在多线程环境下,如何防止自己的变量被其他线程篡改?

api:

void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。

T get():返回此线程局部变量的当前线程副本中的值。

void remove():移除此线程局部变量当前线程的值。

 

1、ThreadLocal 的底层实现是用ThreadLocalMap(它是ThreadLocal的静态内部类,ThreadLocalMap中的Entry继承WeakReference),每一个线程都存在一个ThredLocalMap,其中的元素的 key 值为当前的ThreadLocal对象,而值对应线程的变量副本 。当调用 get、set 方法时,使用的是当前在运行的线程的ThreadLocalMap对象,从而可以获取、设置当前在运行的线程的私有变量。

部分源码:

/* ThreadLocal values pertaining to this thread. This map is maintained

* by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;

2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,再根据当前的ThreadLocal对象,获取到Entry,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocalMap中。

部分源码:

public T get() {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null)

return (T)e.value;

}

return setInitialValue();

}



private T setInitialValue() {  

    T value = initialValue();  //null

    Thread t = Thread.currentThread();  

    ThreadLocalMap map = getMap(t);  

    if (map != null)  

        map.set(this, value);  

    else  

        createMap(t, value);  

    return value;  

}  

3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。跟get()方法差不多。

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

 

eg:

public class MyTest {

//一般将ThreadLocal的对象修饰为static对象,方便多个线程共享

public static ThreadLocal threadLocal = new ThreadLocal<>();

public static void main(String[] args) {

    Thread t1 = new Thread(new MyTask());

    Thread t2 = new Thread(new MyTask());

    Thread t3 = new Thread(new MyTask());

    //启动3个线程

    t1.start();

    t2.start();

    t3.start();

}

}

class MyTask implements Runnable{

    @Override

    public void run() {

        MyTest.threadLocal.set("0");

        //线程计算5次

         for(int i=1;i<5;i++){

             MyTest.threadLocal.set( MyTest.threadLocal.get().toString()+i);

         }

         System.out.println("线程"+Thread.currentThread().getName()+"的计算结果:"+MyTest.threadLocal.get());

    }

}

运行结果:

线程Thread-1的计算结果:01234

线程Thread-0的计算结果:01234

线程Thread-2的计算结果:01234

是不是感觉好神奇!3个线程都是使用同一个 ThreadLocal的静态变量对象 来存储或者获取值,但是3个线程的计算结果却是互不影响,相互独立。 没错,这就是ThreadLocal的解决并发的方式,它为每个线程维护着一个线程的私有变量,同时,它又可以被所有线程所共享。

 

ThreadLocalMap源码:在插入过程中,

private void set(ThreadLocal<?> key, Object  value) {

            // We don't use a fast path as with  get() because it is at

            // least as common to use set() to  create new entries as

            // it is to replace existing ones,  in which case, a fast

            // path would fail more often than  not.

            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) {

                    replaceStaleEntry(key,  value, i);

                    return;

                }

            }

            tab[i] = new Entry(key, value);

            int sz = ++size;

            if (!cleanSomeSlots(i, sz) && sz >=  threshold)

                rehash();

        }

根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下: 1、如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上; 2、不巧,位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value; 3、很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;

长度和hashMap的长度相同(16),但是数组中的元素Entry不是一个链表。

 

Entry源码(ThreadLocalMap的静态内部类):

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是弱引用的,如果发生GC那么key就会被回收掉,此时ThreadLocalMap中就会存在key为null的Entry,但却无法访问key为null的Entry的value值,如果线程还未结束的话,该Entry就不会回收,发生内存泄漏。

解决内存泄漏问题:

调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象(get方法如果没有找到对应的Entry那么就会重新设置给ThreadLocal设置一个null值,set方法可能会将原来的Entry覆盖掉),这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。

 

WeakHashMap如何解决内存泄漏问题:

WeakHashMap$Entry:

private static class Entry<K,V> extends  WeakReference<Object> implements Map.Entry<K,V>  {

        V value;

        final int hash;

        Entry<K,V> next;

        /**

         * Creates new entry.

         */

        Entry(Object key, V value,

              ReferenceQueue<Object> queue,

              int hash, Entry<K,V> next) {

            super(key, queue);

            this.value = value;

            this.hash  = hash;

            this.next  = next;

        }

}//Entry继承WeakReference,所以引用队列中存放的是Entry对象,但是 super(key, queue);所以只有key是弱引用

    

private void expungeStaleEntries() {

        for (Object x; (x = queue.poll()) !=  null; ) {

            synchronized (queue) {

                @SuppressWarnings("unchecked")

                    Entry<K,V> e = (Entry<K,V>)  x;

                int i = indexFor(e.hash,  table.length);

                Entry<K,V> prev = table[i];

                Entry<K,V> p = prev;

                while (p != null) {

                    Entry<K,V> next = p.next;

                    if (p == e) {

                        if (prev == e)

                            table[i] = next;

                        else

                            prev.next = next;

                        // Must not null out  e.next;

                        // stale entries may be  in use by a HashIterator

                        e.value = null; // Help  GC

                        size--;

                        break;

                    }

                    prev = p;

                    p = next;

                }

            }

        }

    }

解决内存泄漏问题主要是expungeStaleEntries方法,它将去翻找ReferenceQueue(存放GC后的Entry对象)中的Entry,然后去WeakHashMap的entry数组中找到索引,然后从对应的链中设置相关的Entry的value为null,到这里就完成了相关数据的清理,put、get、remove、size等方法都能够触发expungeStaleEntries方法,但如果不调用这些方法那么可能会发生内存泄漏。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值