Java並發-安全執行緒策略__Java並發

來源:互聯網
上載者:User
安全執行緒策略 1. 不可變對象 1.1 滿足條件 對象建立以後其狀態就不能改變 對象所有域都是final類型 對象是正確建立的(在對象建立期間,this引用沒有逸出,即將類聲明為final,將成員全部聲明為私人成員,對變數不停set方法,參考String類型) 2. 聲明不可變對象 2.1 Collections.unmodifiableXXX

它的主要實現是將現在的集合對象傳入到Collections.unmodifiableXXX方法中
通過:

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

返回一個自訂的UnmodifiableMap對象,此方法允許模組為使用者提供對內部映射的“唯讀”訪問。在返回的映射上執行的查詢操作將“讀完”指定的映射。試圖修改返回的映射(不管是直接修改還是通過其 collection 視圖進行修改)將導致拋出 UnsupportedOperationException。

Collection、List、Set、Map等 我們只需要把要聲明的集合類型對象傳入到這個方法裡面,這個集合類型就不可以被修改了。

import com.liuyao.concurrency.annotations.TreadSafe;import java.util.Collections;import java.util.HashMap;import java.util.Map;/** * Created By liuyao on 2018/4/17 17:15. */@TreadSafepublic class ImmutableExample2 {    private  static Map<Integer,Integer> map=new HashMap<>();    static {        map.put(1,2);        map.put(2,3);        map= Collections.unmodifiableMap(map);    }    public static void main(String[] args) {        map.put(1,3);  //將會拋出異常        System.out.println(map.get(1));    }}
2.2 ImmutableXXX

Guava:Collection、List、Set、Map 提供了帶初始化資料的方法,一旦初始化完成就不可以被修改了。

package com.liuyao.concurrency.example.immutable;import com.google.common.collect.ImmutableList;import com.google.common.collect.ImmutableMap;import com.google.common.collect.ImmutableSet;import com.liuyao.concurrency.annotations.ThreadSafe;/** * Created By liuyao on 2018/4/17 17:33. */@ThreadSafepublic class ImmutableExample3 {    private final static ImmutableList 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,5,6);    private final static ImmutableMap<Integer,Integer> map2=ImmutableMap.<Integer,Integer>builder().put(1,2).put(3,4).build();    public static void main(String[] args) {//        list.add(4);//        map.put(1,3);        System.out.println(map.get(2));    }}
3. 線程封閉 Ad-hoc線程封閉:程式控制實現,最糟糕的,忽略 堆棧封閉:局部變數,無並發問題 ThreadLocal線程封閉:特別好的封閉方法 4. 線程不安全類和寫法

StringBuilder:線程不安全,方法內部使用,堆棧封閉

StringBuffer:安全執行緒的,用了Synchronized,效能較差 4.1 SimpleDateFormat

不是安全執行緒的,在多線程的情況下,將會出現異常。

多個線程共用一個,線程不安全:

 private  static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd");    private static void update(){        try {            simpleDateFormat.parse("2018-04-19");        } catch (ParseException e) {            e.printStackTrace();        }    }

每次申請一個新的使用,安全執行緒:

private static void update(){    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd");    try {        simpleDateFormat.parse("2018-04-19");    } catch (ParseException e) {        e.printStackTrace();    }}
4.2 JodaTime:安全執行緒的

pom.xml

    <dependency>        <groupId>joda-time</groupId>        <artifactId>joda-time</artifactId>        <version>2.9</version>    </dependency>

用法:

private static DateTimeFormatter dateTimeFormatter= DateTimeFormat.forPattern("yyyy-MM-dd");private static void update(){    DateTime.parse("2018-04-19",dateTimeFormatter).toDate();}
5. 同步容器

ArrayList HashSet HashMap都是線程不安全的 5.1 ArrayList -> Vector,Stack

對於Vector雖然兩個方法都是Synchronized的,但也可能存線上程不安全的情況

import java.util.Vector;/** * Created By liuyao on 2018/4/20 18:00. */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(){               @Override               public void run() {                   for (int i = 0; i < vector.size(); i++) {                       vector.remove(i);                   }               }           };           Thread thread2=new Thread(){               @Override               public void run() {                   for (int i = 0; i < vector.size(); i++) {                       vector.get(i);                   }               }           };           thread1.start();           thread2.start();       }    }}

上訴兩個方法執行時,由於get方法在取值的時候,很有可能另一個線程的remove方法已經移除該元素的了,故拋出了異常。兩個線程執行順序的差異導致的同步問題。 5.2 HashMap -> HashTable(key,value不能為null) #### 5.3 Collections.synchronizedXXX(List,Map,Set)

 private static List<Integer> list= Collections.synchronizedList(new ArrayList<Integer>());
5.4 集合邊遍曆邊刪除
import com.liuyao.concurrency.annotations.NotThreadSafe;import java.util.Iterator;import java.util.Vector;/** * Created By liuyao on 2018/4/20 18:00. */@NotThreadSafepublic class VectorExample3 {    //java.util.ConcurrentModificationException    public static void test1(Vector<Integer> v1){        for (Integer i :v1) {            if (i.equals(3)){                v1.remove(i);            }        }    }//    java.util.ConcurrentModificationException    public static void test2(Vector<Integer> v2){        Iterator<Integer> iterator=v2.iterator();        while (iterator.hasNext()){            Integer i=iterator.next();            if (i.equals(3)){                v2.remove(i);            }        }    }//    success    public static void test3(Vector<Integer> v3){        for (int i = 0; i < v3.size(); i++) {            if (v3.get(i).equals(3)){                v3.remove(i);            }        }    }    public static void main(String[] args) {        Vector<Integer> vector=new Vector<>();        vector.add(1);        vector.add(2);        vector.add(3);        test3(vector);    }}

當集合遍曆是對集合進行remove操作會導致:

final void checkForComodification() {        if (modCount != expectedModCount)            throw new ConcurrentModificationException();    }

上面的modCount和expectedModCount不相等,拋出異常,所以使用了foreach迴圈和迭代器迴圈,盡量不要在早操作過程中進行remove操作,可以先標記等遍曆完再進行remove操作。單線程都會出錯,多線程情況下,出現錯誤的情況更大。 6. 並發容器 J.U.C 6.1 ArrayList -> CopyOnWriteArrayList

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

寫操作複製,當有新的元素添加到數組中時,先從原有的數組中拷貝一份出來,在新的數組中進行寫操作,然後將原來的數組指向新的數組上。整個寫操作都是在鎖的保護下進行的,主要是為了多線程並發時,複製多個數組出來把數組搞亂了。

因為寫操作要複製元素,如果元素較多的情況下,可能會導致GC,不能用於即時讀的情境,由於拷貝數組和新增元素都需要時間,所以不能滿足即時性,但能滿足最後的結果是正確的。

CopyOnWriteArrayList適合讀多寫少的情況。當你無法保證CopyOnWriteArrayList到底要放置多少資料時,建議不要使用,因為每次更新操作,都會複製操作,這個代價有點大,可能會引起效能故障。

三個思想:
1. 讀寫分離,讀不需要加鎖,寫要加鎖
2. 最終一致性,保證最終是對的
3. 另外開闢空間來解決並發衝突 6.2 HashSet,TreeSet -> CopyOnWriteArraySet,ConcurrentSkipListSet

CopyOnWriteArraySet底層實現是用了CopyOnWriteArrayList,適用於大小很小的set,讀操作大於寫操作。add,set,remove操作開銷較大。

ConcurrentSkipListSet是JDK6新增的類,是自然排序的,並且可以自訂比較子,基於Map集合的額,多線程情況下對於add,remove,contains是安全執行緒的,對addAll,removeAll,containsAll不是同步的,他們底層調用的還是上面的這些單個方法,大量操作時只能一次的是執行緒安全性的,需要自己手動加鎖。

ConcurrentSkipListSet不允許使用空元素,它無法可靠的將元素的傳回值與不存在的元素區分開來。 6.3 HashMap,TreeMap -> ConcurrentHashMap,ConcurrentSkipListMap

ConcurrentHashMap不允許有空值,對讀做了大量最佳化

ConcurrentSkipListMap的key有序,支援更高的並發,存取時間和線程數沒關係,並發線程數越多,效果越明顯。

7. 安全共用對象策略-總結 線程限制:一個被線程限制的對象,有線程獨佔,並且只能被佔有它的線程修改 共用唯讀:一個共用唯讀對象,在沒有額外同步情況下,可以被多個線程並發訪問,但是任何線程都不能修改它 安全執行緒對象:一個安全執行緒的對象或者容器,在內部通過同步機制來保證安全執行緒,所以其他線程無需額外的同步就可以通過公用介面隨意訪問它 被守護對象:被守護對象只能通過擷取特定的鎖來訪問。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.