Java ThreadLocal詳解

 

    在多線程開發中,ThreadLocal是很是常見的Java API。它能夠輕鬆的建立一個線程安全的變量,使得每個線程擁有各自的實例變量。java

    如下代碼爲每個線程分配了一個線程ID。    web

public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }

 

ThreadLocal如何作到一個實例存儲了不一樣線程的值?tomcat

           跟蹤ThreadLocal get()方法能夠發現,它先獲取了當前線程Thread.currentThread,再從當前線程中獲取ThreadLocalMap實例。理一下思路,實際上每個線程擁有一個ThreadLocalMap實例,這個Map存儲了當前線程的值。安全

        進一步跟蹤ThreadLocalMap。它以WeakReference<ThreadLocal>作Key,Object作Value。ThreadLocal作key的做用?爲什麼須要定義成WeakReference?多線程

package com.sd.concurrent;

import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * Created by sunda on 17-8-23.
 */
public class ThreadLocalTest {

    class User {
        String name;
        String desc;

        public User() {
        }

        public User(String desc, String name) {
            this.desc = desc;
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "ValueHolder{" +
                    "desc='" + desc + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }

    class Product{
        String name;
        String desc;

        public Product() {
        }

        public Product(String desc, String name) {
            this.desc = desc;
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
    ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
    ThreadLocal<Product> productThreadLocal = new ThreadLocal<>();
    @Test
    public void test() throws InterruptedException {

        Thread aaThread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                userThreadLocal.set(new User("aa", "aa11"));
                productThreadLocal.set(new Product("aa", "aa11"));

                try {
                    TimeUnit.SECONDS.sleep(1);
                    User valueHolder = userThreadLocal.get();
                    System.out.println("aa "+valueHolder);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        aaThread1.start();

        Thread bbthread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                userThreadLocal.set(new User("bb", "bb11"));
                productThreadLocal.set(new Product("bb", "bb11"));

                try {
                    TimeUnit.SECONDS.sleep(1);
                    User valueHolder = userThreadLocal.get();
                    System.out.println("bb "+valueHolder);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        bbthread1.start();

        User valueHolder = userThreadLocal.get();
        System.out.println("main  "+valueHolder);

        TimeUnit.SECONDS.sleep(300);

    }
}

        以上代碼,兩個ThreadLocal變量,兩個Thread。同一個線程擁有兩個ThreadLocal變量的時候,須要互相區分,防止值被覆蓋。ide

        在HashMap中,若是以Object作key,咱們都會複寫HashCode方法。一樣的,在ThreadLocal中,有一個名爲threadLocalHashCode的變量,它作爲key的hashCode。在設計hashCode的時候,咱們會生成一個儘量稀疏的值,來獲取更高讀寫的性能。性能

        在ThreadLocal,它以遞增一個固定值的方式給ThreadLocal實例分配threadLocalHashCode。該值是精心設計的,在map中能夠均勻分佈。this

        設計成WeakReference是爲了儘早的垃圾回收.Thread的建立和銷燬都須要耗必定的資源,一般建立完以後會重複使用,每每生命週期很長,而線程中的實例則否則,須要更早的被回收.變量定義成WeakReference將在GC觸發後被回收。線程

          ThreadLocal爲咱們提供了很是大的便利,可是不正確的使用很容易引發較爲隱晦的內存泄漏,這種內存泄漏常見於web程序,例如tomcat下運行的一段代碼.            設計

public class UserIfaceThreadLocal {

    public static ThreadLocal<UserIface> userIfaceThreadLocal = new ThreadLocal<>();
   .......

    
}

           該程序乍看起來毫無問題,前期也能穩定運行,可是過一段時間後,會在永久區溢出.出現這種問題甚至重啓web也無濟於事.這段"經典"的代碼是ClassLoader溢出的範例.

           在分析以前,有兩點須要注意:

                1:ThreadLocal實例被申明爲static的類變量.它從ClassLoader加載後存活,直到最後Class被回收.

                2:Tomcat做爲web容器,它的工做線程存活時間比web程序更長.web程序的全部Class都是被tomat的ClassLoader加載進去的.               

           假設web程序終止,JVM開始回收上述Class,這時候發現有一個ThreadLocal實例的存活.因爲tomcat的工做線程並未終止,UserIface仍被線程引用到.因而classloader沒法被回收,致使了全部的Class文件仍在JVM中,沒法卸載.

            在使用ThreadLocal的時候,須要像使用資源同樣去處理,建立出來須要正確"關閉".在上述例子中,須要主動調用remove清空.

相關文章
相關標籤/搜索