• 多线程(8) — ThreadLocal


      ThreadLocal是一个线程的局部变量,也就是只有当前线程可以访问,是线程安全的。为每一个线程分配不同的对象,需要在应用层面保证ThreadLocal只起到简单的容器作用。

    ThreadLocal类很简单,只有4个方法,它们是如下方法:

    • void set(Object value)设置当前线程的线程局部变量的值。
    • public Object get()该方法返回当前线程所对应的线程局部变量
    • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显示调用该方法清楚线程的局部变量并不是必须的操作,但它可以加快内存回收速度。
    • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第一次调用get()或者set()时才会执行,并且仅执行一次。ThreadLocal中的缺省实现直接返回一个null。

    ThreadLocal如何保证这些对象只被当前线程访问?先从set()方法说起:

    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时先获得当前线程对象,然后通过getMap()方法拿到线程的ThreadLocalMap,并将值存入ThreadLocalMap中。这个map中key是ThreadLocal当前对象,value就是我们需要的值。而在get()方法操作时,自然就是将这个Map中数据拿出来。

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

      get()方法先取得当前线程的ThreadLocalMap对象,然后通过将自己作为key取得内部的实际数据。这样会有个问题:这些变量是维护在Thread类内部的,也就是线程不退出,对象的引用一直存在。Thread退出时的代码如下:

    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        target = null;
        /* 加速资源清理 */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

      因此,使用线程池就意味着当前线程未必会退出(比如固定大小的线程池,线程总是存在的)。这样的话,将一些大的对象放在ThreadLocal中可能会使系统出现内存泄露的可能,设置对象后使用几次不清理它,对象不再有用,但已经无法收回。如果希望及时回收对象,可以使用ThreadLocal.remove()方法将变量移除。对于ThreadLocal变量,手动设置为null,这个ThreadLocal对应的所有线程的局部变量都有可能被收回。下面就是个例子

    package com.wyw.xc.test.threadlocal;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ThreadLocalDemo_Gc {
    
        static volatile ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected void finalize() throws Throwable{
                System.out.println(this.toString()+" is gc");
            }
        };
        static volatile CountDownLatch cd = new CountDownLatch(10000);
        public static class ParseDate implements Runnable{
    
            int i = 0;
            public ParseDate(int i){
                this.i= i;
            }
            
            
            @Override
            public void run() {
                try {
                    if(tl.get()==null){
                        tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"){
                            
                            @Override
                            protected void finalize() throws Throwable{
                                System.out.println(this.toString()+ " is gc ");
                            }
                        });
                        System.out.println(Thread.currentThread().getId()+":create SimpleDateFormat");
                    }
                    Date t = tl.get().parse("2019-08-04 18:40"+i%60);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally{
                    cd.countDown();
                }
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            ExecutorService es = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 10000; i++) {
                es.execute(new ParseDate(i));
            }
            cd.await();
            System.out.println("mission complete!!");
            tl=null;
            System.gc();
            System.out.println("first GC complete!");
            tl = new ThreadLocal<SimpleDateFormat>();
            cd = new CountDownLatch(10000);
            for (int i = 0; i < 10000; i++) {
                es.execute(new ParseDate(i));
            }
            cd.await();
            Thread.sleep(1000);
            System.gc();
            System.out.println("second GC complete!!");
        }
    }

      首先线程池中10个线程各自创建了一个SimpleDateFormat对象实例,接着进行第一次GC,可以看到ThreadLocal对象被收回。第二次提交任务时,这次也创建了10个SimpleDateFormat对象,然后进行第二次GC。第二次GC后第一次创建的10个对象全部回收,虽然我们没有手工remove()这些对象,但是系统仍然有可能回收它们。

    为每一个线程分配一个独立的对象对系统性能是有帮助的,如果共享对象对于竞争的处理容易引起性能损失,我们还是应该考虑使用ThreadLocal为每个线程分配单独的对象。

  • 相关阅读:
    matlab练习程序(单源最短路径Dijkstra)
    Android开发必须知道SERVICE的10件事
    Android 多种方式正确的加载图像,有效避免oom
    在Android中解决内存溢出 – OutOfMemoryError
    发布Android开源库,看这个文章就够了!
    发掘StateListAnimator的全部潜能
    Android开发中多进程共享数据
    Android使用FFMpeg实现推送视频直播流到服务器
    Android学Jni/Ndk 开发记录(一)
    一张图解释RxJava中的线程控制
  • 原文地址:https://www.cnblogs.com/wangyongwen/p/11273146.html
Copyright © 2020-2023  润新知