Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> ThreadLocal

ThreadLocal

編輯:關於Android編程

先上一個使用ThreadLocal實例的demo,ThreadLocalDemo 實例包含一個ThreadLocal實例。從網上各種信息看到ThreadLocal是線程私有變量。保持了每個變量的副本,其實ThreadLocal不能用於解決多線程共享變量問題。
ThreadLocal 中只是保存該線程自己創建的局部變量的副本。如果是多線程共享的變量還是會發生不能同步該的後果。下面這個例子就是啟動兩個線程,通過threadLocal實例的set方法將person實例加入到線程本地變量ThreadLocal.Values localValues;中。但是localValues這個變量底層實現十基於數組的一個map結構。對於引用變量緩存引用。所以在這個demo中兩個線程的localValues變量都指向了同一個person實例。也就不是線程私有的變量。要達到線程私有的話只有在線程中通過創建的變量通過ThreadLocal的set方法插入的元素才是線程私有的變量。


public class ThreadLocalDemo {
    private static Person person;
    private static ThreadLocal threadLocal = new ThreadLocal();

    public ThreadLocalDemo( Person person ) {
        this.person = person;
    }

    public static void main( String[] args ) throws InterruptedException {
        // 多個線程使用同一個Person對象
        Person per = new Person(111, "Sone");
        ThreadLocalDemo test = new ThreadLocalDemo(per);
        Thread th1 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(person);
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(888);
                perLocal.setName("Admin");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th1");
        Thread th2 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(person);
                System.out.println(Thread.currentThread().getName() + " :"
                        + threadLocal.get());
            }
        }, "thread-th2");
        th1.start();
        th1.join();
        Thread.sleep(100);
        th2.start();

        th2.join();
        // Person對象已經被線程給修改了!
        System.out.println("Person對象的值為:" + per);
    }


}

 class Person {

    private int id;
    private String name;

    public Person( int id , String name ) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

輸出:
可以看到雖然將person實例加入到兩個線程本地變量ThreadLocal.Values localValues; 但是都是指向同一個實例person。所以從輸出結果可以看到在th1線程更改了person後,在th2線程也可以獲取到最新的結果。

thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=888, name=Admin]
thread-th2 :Person [id=888, name=Admin]
Person對象的值為:Person [id=888, name=Admin]

下面給出在線程裡面new一個實例然後通過ThreadLocal類的set方法插入到當前線程的ThreadLocal.Values localValues;變量中 ,最後對當前線程的localValues變量中的本地變量通過ThreadLocal類的get()方法獲取到當前線程threadLocal實例為key對於的值。


public class ThreadLocalDemo {
    private static Person person;
    private static ThreadLocal threadLocal = new ThreadLocal();
//定義一個ThreadLocal類實例,這個是插入線程本地變量的接口類,
//有get/set方法;ThreadLocal類其實只是封裝了插入線程本地變量的操作接口,
//每個線程的線程本地變量ThreadLocal.Values localValues就是一個map存儲
//結構,以ThreadLocal類實例為key,存儲的數據為值。
//如果需要獲取到這個本地變量,只需要在線程內部通過ThreadLocal類實例的get()方法就可以獲取到與ThreadLocal類實例對於的值。

    public ThreadLocalDemo( Person person ) {
        this.person = person;
    }

    public static void main( String[] args ) throws InterruptedException {
        // 多個線程使用同一個Person對象
        Person per = new Person(111, "Sone");
        ThreadLocalDemo test = new ThreadLocalDemo(per);
        Thread th1 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(new Person(111, "Sone"));
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(9999);
                perLocal.setName("jim");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th1");
        Thread th2 = new Thread(new Runnable() {

            public void run() {
                // TODO Auto-generated method stub
                threadLocal.set(new Person(112, "vincent"));
                String threadName = Thread.currentThread().getName();
                Person perLocal = threadLocal.get();
                System.out.println(threadName + " before:" + threadLocal.get());
                perLocal.setId(8);
                perLocal.setName("jack");
                System.out.println(threadName + " after:" + threadLocal.get());
            }
        }, "thread-th2");
        th1.start();
        th2.start();
        th1.join();
        th2.join();
        // Person對象已經被線程給修改了!
        System.out.println("Person對象的值為:" + per);
    }

    public void run() {

    }
}

 class Person {

    private int id;
    private String name;

    public Person( int id , String name ) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Person [id=" + id + ", name=" + name + "]";
    }
}

輸出結果:
從輸出結果可以看到我在各自線程創建一個person實例,然後插入到線程本地變量中(ThreadLocal.Values localValues; 是一個map結構的數據結構),每個線程有具有一個這樣的實例;插入使用的是相同的ThreadLocal類實例為key,但是緩存的是不同person變量。

thread-th2 before:Person [id=112, name=vincent]
thread-th2 after:Person [id=8, name=jack]
thread-th1 before:Person [id=111, name=Sone]
thread-th1 after:Person [id=9999, name=jim]
Person對象的值為:Person [id=111, name=Sone]

最後說下看ThreadLocal類後對他的理解,首先ThreadLocal不是一個具體的線程。它是一個線程用於存取本地變量 ThreadLocal.Values localValues;的操作類,localValues是一個map類型的數據,key就是ThreadLocal,value就是插入的數據,在一個線程中可以插入不同ThreadLocal實例的數據,一個線程本地變量只能緩存特定ThreadLocal實例的一條數據。

在java中ThreadLocal以Map的形式存儲數據(ThreadLocal對象為 key 數值為value)。在Android中做了些改動,在Thread-Local的add方法中,可以看到它會把ThradLocal對象(key)和相對應的value放在table數組連續的位置中。 也就是table被設計為下標為0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。

先看下android的ThreadLocal類的源碼,其中也就兩個接口方法重要,get()和set(T data);

ThreadLocal數據插入流程

//set(T data)讓線程插入一個key為當前ThreadLocal實例,value為value的鍵值對
   public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
//values(Thread current)這裡就可以看到其實ThreadLocal類操縱的還是當前線程的本地變量。
Values values(Thread current) {
        return current.localValues;
    }
   //然後對values判定是否為空,如果為空那麼初始化一個空的Values實例,
   //如下圖就是初始化了一個空的Values類實例復制給了當前線程的
   //ThreadLocal.Values localValues屬性字段;
   Values initializeValues(Thread current) {
        return current.localValues = new Values();
    }
Values() {
            initializeTable(INITIAL_SIZE);
            this.size = 0;
            this.tombstones = 0;
        }
        /**
         * Creates a new, empty table with the given capacity.
         */
        private void initializeTable(int capacity) {
            this.table = new Object[capacity * 2];
            this.mask = table.length - 1;
            this.clean = 0;
            this.maximumLoad = capacity * 2 / 3; // 2/3
        }
//最後調用Values實例的put方法完成了數據插入到map中,可以清晰看到key為ThreadLocal類實例,value就是set方法傳進來的數據。

//下面是Values類的put方法處理邏輯,看到for循環時候,尋找插入位置時候先匹配到key,而key存放的位置比較特殊在數組下標的0 ,2, 4 ,6 ... 2n;這些位置,value存放位置在1,3,5,7...2n+1這些位置。


  void put(ThreadLocal key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;
            //index是尋找key存放的下標, key.hash & mask尋找循環的起止位置,mask是table.length-1,默認是31,key.hash & mask計算後使得index一定指向key的下標。next()方法是對index加2操作。
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

ThreadLocal數據獲取流程

//get獲取數據
 public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);//獲取到當前線程的本地變量map
        if (values != null) {
            Object[] table = values.table;
          int index = hash & values.mask;//計算這個鍵值對存放的位置
            if (this.reference == table[index]) {
                return (T) table[index + 1];//返回結果
            }
        } else {
            values = initializeValues(currentThread);//如果當前線程沒有本地變量,初始化一個空的
        }

        return (T) values.getAfterMiss(this);//如果確實返回一個默認值。
    }

首先得到一個Values對象,然後求出table數組ThreadLocal實例reference屬性的下標。前文說過:ThradLocal對象(key)和相對應的value放在table數組連續的位置中。 也就是table被設計為下標為0,2,4…2n的位置存放key,而1,3,5…(2n +1 )的位置存放value。現在得到index後再index+1就是value在table數組中的下標。即value=table[index+1];return value即可。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved