線程安全策略

1.不可變對象

1.1.不可變對象需要滿足的條件:

① 對象創建後其狀態就不能修改;

② 對象所有域都是final類型的;

③ 對象是正確創建的(在對象創建期間,this引用沒有逸出);

1.2. final關鍵字

① final可以用來修改類 方法 變量;

② final修飾的類不能被繼承(我們平時使用的基礎數據類型的類都是final的);

③ final修飾方法不能被繼承類修改,並且在一定程度上會提升效率;

④ final修飾變量如果是基本數據類型則初始化後就不能修改變量了,如果是引用數據類型則在初始化後就不能更改其引用;

1.修飾類詳解:

        當用final修飾一個類時,表明這個類不能被繼承,final類中的成員比那輛可以根據需要設爲final修飾的,但是需要注意,final類中所有的成員方法都會被飲食的指定爲final方法。在使用final修飾類的時候,要注意謹慎選擇,除非這個類不會用來繼承或出於安全考慮,否則儘量不要把類設計爲final類。

2.修飾方法:

使用final方法的原因有兩個,一個原因是把方法鎖定,以防任何繼承類修改它的含義。第二個原因是效率。在早期的Java版本中,會將final方法轉化爲內嵌調用。但如果方法過於龐大,可能看不到內嵌調用帶來的性能提高,所以在最近的Java版本中,不需要使用final方法進行優化了。

因此,如果只有在想明確禁止,該方法在子類中被覆蓋的情況下才將方法置爲final的。注意:類的private方法會隱式的被指定爲final方法。

3.修飾變量

對於一個final變量,如果是基本數據的類型,則數值一旦在初始化後便不能更改,如果是引用類型的變量,則在對其初始化後便不能再讓其指向另一個對象。

 

1.3.代碼演示

@Slf4j
@NotThreadSafe
public class ImmutableExample1 {

    private final static Integer a = 1;
    private final static String b = "2";
    private final static Map<Integer, Integer> map = Maps.newHashMap();

    static {
        map.put(1, 2);
        map.put(3, 4);
        map.put(5, 6);
    }

    public static void main(String[] args) {
//        a = 2;
//        b = "3";
//        map = Maps.newHashMap();
        map.put(1, 3);
        log.info("{}", map.get(1));
    }

    private void test(final int a) {
//        a = 1;
    }
}

Java中除了final可以用來修飾不可變對象以外還有其他的方法也可以來修飾不可變對象比如:

①以Collections.unmodifiableXXX爲前綴的方法,包括Collection List Set Map等...

②Guava : ImmutableXXX : Collection List Set Map等...

1.4. Collections.unmodifiableXXX修飾不可變對象

@Slf4j
@ThreadSafe
public class ImmutableExample2 {

    private static Map<Integer, Integer> map = Maps.newHashMap();

    static {
        map.put(1, 2);
        map.put(3, 4);
        map.put(5, 6);
        map = Collections.unmodifiableMap(map);
    }

    public static void main(String[] args) {
        map.put(1, 3);
        log.info("{}", map.get(1));
    }

}

 

從上圖中可以看出經過Collections.unmodifiableMap修飾以後這個map就不可變了,如果強制put會拋出異常;接下來我們來看一下源碼

public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) {
        return new UnmodifiableMap<>(m);
    }

在底層創建了一個UnmodifiableMap然後將原來map中的數據複製到這個新的map中以後,將map中的方法修改,修改成拋出異常如下所示

@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            throw new UnsupportedOperationException();
}

1.5. Guava : ImmutableXXX修飾不可變對象

@ThreadSafe
public class ImmutableExample3 {

    private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);

    private final static ImmutableSet set = ImmutableSet.copyOf(list);

    private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);

    private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder()
            .put(1, 2).put(3, 4).put(5, 6).build();


    public static void main(String[] args) {
        System.out.println(map2.get(3));
    }
}

 

2.線程封閉

 

2.1 概念

當訪問共享的可變數據時,通常需要使用同步。一種避免使用同步的方式就是不共享數據。如果僅在單線程內訪問數據,就不需要同步。這種技術被稱爲線程封閉。

2.2 實現線程封閉的幾種方法

① Ad-hoc線程封閉:程序控制實現,效果最差可以直接忽略;

②堆棧封閉:局部變量,無併發問題;

③ThreadLocal線程封閉:特別好的封閉方法;

2.3 ThreadLocal線程封閉代碼實現

// 聲明RequestHolder類
public class RequestHolder {

    private final static ThreadLocal<Long> requestHolder = new ThreadLocal<>();

    public static void add(Long id) {
        requestHolder.set(id);
    }

    public static Long getId() {
        return requestHolder.get();
    }

    public static void remove() {
        requestHolder.remove();
    }
}


// 定義HttpFilter
@Slf4j
public class HttpFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        log.info("do filter, {}, {}", Thread.currentThread().getId(), request.getServletPath());
        RequestHolder.add(Thread.currentThread().getId());
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

    }
}


// 聲明HttpInterceptor
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle");
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestHolder.remove();
        log.info("afterCompletion");
        return;
    }
}


// 配置啓動類ConcurrencyApplication
@SpringBootApplication
@EnableHystrixDashboard
@EnableCircuitBreaker
public class ConcurrencyApplication extends WebMvcConfigurerAdapter{

    public static void main(String[] args) {
        SpringApplication.run(ConcurrencyApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean httpFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new HttpFilter());
        registrationBean.addUrlPatterns("/threadLocal/*");
        return registrationBean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HttpInterceptor()).addPathPatterns("/**");
    }
}


// 聲明ThreadLocalController類
@Controller
@RequestMapping("/threadLocal")
public class ThreadLocalController {

    @RequestMapping("/test")
    @ResponseBody
    public Long test() {
        return RequestHolder.getId();
    }
}

3.線程不安全類與寫法

 

3.1 StringBuilder與StringBuffer

// StringBuilder
@Slf4j
@NotThreadSafe
public class StringExample1 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static StringBuilder stringBuilder = new StringBuilder();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", stringBuilder.length());
    }

    private static void update() {
        stringBuilder.append("1");
    }
}

 

// StringBuffer
@Slf4j
@ThreadSafe
public class StringExample2 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    public static StringBuffer stringBuffer = new StringBuffer();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", stringBuffer.length());
    }

    private static void update() {
        stringBuffer.append("1");
    }
}

     通過這兩個例子我們可以看出在多線程情況下StringBuffer是線程安全的,而StringBuilder是線程不安全的,其實StringBuffer底層實現的時候是加了synchronized關鍵字的而StringBuilder則沒有加這個關鍵字;可以要提供兩個功能基本相同的類呢?因爲StringBuffer雖然是線程安全的,但是效率較低而StringBuilder的效率是相較於StringBuffer略高的;

3.2 ArrayList HashSet HashMap等

 

// ArrayList
@Slf4j
@NotThreadSafe
public class ArrayListExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

// HashSet
@Slf4j
@NotThreadSafe
public class HashSetExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Set<Integer> set = new HashSet<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }

    private static void update(int i) {
        set.add(i);
    }
}

// HashMap
@Slf4j
@NotThreadSafe
public class HashMapExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Map<Integer, Integer> map = new HashMap<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }

    private static void update(int i) {
        map.put(i, i);
    }
}

       通過我們的演示,我們已經看到ArrayList HashSet HashMap不是線程安全的,但是我們平時使用的時候爲什麼沒有察覺到呢?那是因爲我們在使用的時候一般都是作爲局部變量來使用的所以很少會出現問題;關於ArrayList HashSet HashMap這些類有沒有線程安全的處理類呢,答案是肯定的,而且不止一種,後邊我們會詳細介紹;關於線程不安全的類我們就先介紹到這裏;

4.同步容器

 

在java中同步容器有兩種,一種是像Vector Stack HashTable這樣的提供好的類,另外一種是Collections.synchronizedXXX(List Set Map)提供的一些靜態工廠方法創建的類,這些方法都是以synchronized修飾的方法;

1 Vector : Vector實現了List接口,Vector實際上即使一個數組,Vector中的方法都是使用synchronized修飾的所以是線程安全的同步容器;

// Vector1
@Slf4j
@ThreadSafe
public class VectorExample1 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

可以看到我們在使用Vector以後我們結果已經和預期是一致的了,但是Vector並不是線程安全的

 

// Vector2
@NotThreadSafe
public class VectorExample2 {

    private static Vector<Integer> vector = new Vector<>();

    public static void main(String[] args) {

        while (true) {

            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }

            Thread thread1 = new Thread() {
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        vector.remove(i);
                    }
                }
            };

            Thread thread2 = new Thread() {
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        vector.get(i);
                    }
                }
            };
            thread1.start();
            thread2.start();
        }
    }
}

 

大家可以看到這裏我們的同步容器拋出了異常,可是這是爲什麼呢?Vector裏邊的方法雖然是被synchronized修飾的同步方法,但是由於操作順序的不同在併發的環境還是會出現問題的,就如以上代碼所示那樣;

2 Stack : Stack是一個同步容器,它的方法也是使用synchronized來進行修飾的,而且Stack是繼承與Vector的;關於Stack這裏就不在舉例說明了,因爲它是繼承自Vector的

 

3 HashTable : HashTable實現了Map接口和HashMap非常相似, HashTable進行了同步處理相關的方法也是使用synchronized來進行修飾的;

// HashTable
@Slf4j
@ThreadSafe
public class HashTableExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Map<Integer, Integer> map = new Hashtable<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }

    private static void update(int i) {
        map.put(i, i);
    }
}

 

 

從這裏我們可以看到我們使用HashTable的話這個實例就編程了線程安全的;

4 Collections : Collections它是一個工具提供的類,與Collection是不一樣的, Collections這個類中提供了大量的方法;

// Collections.synchronizedList
@Slf4j
@ThreadSafe
public class CollectionsExample1 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static List<Integer> list = Collections.synchronizedList(Lists.newArrayList());

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

// Collections.synchronizedSet
@Slf4j
@ThreadSafe
public class CollectionsExample2 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Set<Integer> set = Collections.synchronizedSet(Sets.newHashSet());

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }

    private static void update(int i) {
        set.add(i);
    }
}

// Collections.synchronizedMap
@Slf4j
@ThreadSafe
public class CollectionsExample3 {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Map<Integer, Integer> map = Collections.synchronizedMap(new HashMap<>());

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }

    private static void update(int i) {
        map.put(i, i);
    }
}

可以看到我們在使用Collections的同步容器以後,這些實例都編程了線程安全的;

5.併發容器與安全共享策略總結

 

1 併發容器

ArrayList --> CopyOnWriteArrayList

概念 : 簡單的講就是寫操作時賦值,當有新元素添加到CopyOnWriteArrayList時,它先從原有的數組裏邊Copy一份出來然後在新的數組上做些操作,操作完成以後在將引用指向新的數組;CopyOnWriteArrayList所有的操作都是在鎖的保護下進行的,這樣做的目的主要是爲了在多線程併發做add操作的時候複製出多個副本出來導致數據混亂;

缺點 :

① 由於是copy的操作所以比較消耗內存,如果元素的內容較多的時候可能會觸發GC,

② 不能用於實時讀的場景,它比較適合讀多寫少的場景;

思想 :

① 讀寫分離;

② 最終一致性;

③ 另外開闢空間解決併發衝突;

// CopyOnWriteArrayList
@Slf4j
@ThreadSafe
public class CopyOnWriteArrayListExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static List<Integer> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", list.size());
    }

    private static void update(int i) {
        list.add(i);
    }
}

HashSet --> CopyOnWriteArraySet 與 TreeSet --> ConcurrentSkipListSet

概念 :

CopyOnWriteArraySet它是線程安全,底層實現是使用CopyOnWriteArrayList,它的很多特性都與CopyOnWriteArrayList相似包括適用場景;

ConcurrentSkipListSet是jdk6新增的類,支持自然排序,可以在構造的時候自己定義比較器,它是基於Map集合的,在多線程環境下ConcurrentSkipListSet它裏邊的remote add 等方法都是線程安全的,但是對於批量操作並不能保證以原子方式進行操作,在批量操作的時候只能保證每一次的操作是原子性的;ConcurrentSkipListSet在使用批量操作的時候可能需要手動處理一下;

// CopyOnWriteArraySet
@Slf4j
@ThreadSafe
public class CopyOnWriteArraySetExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Set<Integer> set = new CopyOnWriteArraySet<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }

    private static void update(int i) {
        set.add(i);
    }
}

 

// ConcurrentSkipListSet
@Slf4j
@ThreadSafe
public class ConcurrentSkipListSetExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Set<Integer> set = new ConcurrentSkipListSet<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", set.size());
    }

    private static void update(int i) {
        set.add(i);
    }
}

HashMap --> ConcurrentHashMap 與 TreeMap --> ConcurrentSkipListMap

概念 :

ConcurrentHashMap是HashMap線程安全的版本,ConcurrentHashMap不允許空值,在實際的應用中除了少數的插入操作和刪除操作外,絕大多數操作都是讀取操作,而且讀操作大多數都是成功的,基於這個前提ConcurrentHashMap針對讀操作多了特別多的優化,具有特別高的併發性;

ConcurrentSkipListMap是TreeMap線程安全的版本,ConcurrentSkipListMap底層是使用SkipList這種跳錶的結構實現的;

// ConcurrentHashMap
@Slf4j
@ThreadSafe
public class ConcurrentHashMapExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Map<Integer, Integer> map = new ConcurrentHashMap<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }

    private static void update(int i) {
        map.put(i, i);
    }
}

// ConcurrentSkipListMap
@Slf4j
@ThreadSafe
public class ConcurrentSkipListMapExample {

    // 請求總數
    public static int clientTotal = 5000;

    // 同時併發執行的線程數
    public static int threadTotal = 200;

    private static Map<Integer, Integer> map = new ConcurrentSkipListMap<>();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}", map.size());
    }

    private static void update(int i) {
        map.put(i, i);
    }
}

2 J.U.C的實際構成

3 安全共享對象策略總結

1 線程限制 : 一個被線程限制的對象,由線程獨佔,並且只能被佔有它的線程修改;

2 共享只讀 : 一個共享只讀的對象,在沒有額外同步的情況下,可以被多個線程併發訪問,但是任何線程都不能修改它;

3 線程安全對象 : 一個線程安全的對象或者容器,在內部通過同步機制來保證線程安全,所以其他線程無需額外的同步就可以通過公共接口隨意訪問它;

4 被守護對象 : 被守護對象只能通過獲取特定的鎖來訪問;

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