《Java 併發編程》ThreadLock詳解

前言

在併發開發的過程中,我們都知道需要保證共享資源的的讀寫有序。加鎖是我們比較常用的一種方式。ThreadLock則是從另外一個角度出發,每一個線程都獨立資源,這樣同樣可以解決資源的問題。這樣講可能不是很好理解,下面我們通過案例來說明這個情況。

案例

我們在使用日期格式轉換的時候,會出現日期轉換出錯,或者日期不是自己想要的結果。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLockTest {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static class ParseDate implements Runnable {

        int i = 0;

        public ParseDate(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                Date f = sdf.parse("2017-01-15 15:22:" + i/60);
                System.out.println(i + ":" + f);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i ++) {
            es.execute(new ParseDate(i));
        }
    }
}

運行結果:

SimpleDateFormat 本身不是線程安全的類,所以在這樣的情況,我們直接這樣使用是會出現問題的。

當然如果我們直接在線程裏面new SimpleDateFormat來處理也是可以的,但是這樣的話,因爲日期處理一般系統都比較多的一種操作,每次都創建對象,就更加容易讓Jvm出現OOM的情況。還有一種方式,就是每一個線程裏面我們都創建一個SimpleDateFormat來處理線程內部的日期格式化。我們來一起看看如何實現:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLockTest1 {

    static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<>();

    public static class ParseDate implements Runnable {

        int i = 0;

        public ParseDate(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                if (t1.get() == null) {   // 當前線程沒有SimpleDateFormat對象,則創建一個
                    t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                }
                Date f = t1.get().parse("2017-01-15 15:22:" + i/60);
                System.out.println(i + ":" + f);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i ++) {
            es.execute(new ParseDate(i));
        }
    }
}

運行結果:

每一線程執行都正常了。

原理

看看我們用到的兩個方法的源碼實現邏輯:

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

將對象存放到本地線程中。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

獲取對象的時候也從本地線程中獲取。

重點:我們現在系統基本都使用線程池,如果你設置到線程裏面的對象會導致GC無法回收。

如需及時回收對象,則可以使用

ThreadLocal.remove();    -- 清空線程中的對象信息。

總結

該功能用處並不會很廣,比較小衆的使用場景,瞭解即可。

參考:https://blog.csdn.net/mzh1992/article/details/62230075

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章