并发编程-10线程安全策略之不可变对象
文章目錄
- 腦圖
- 四個線程安全策略
- 不可變對象定義
- 不可變對象需要滿足的條件
- 如何創建不可變對象
- 使用final關鍵字定義不可變對象
- 修飾變量示例
- final修飾基本數據類型及String: 初始化之后不能修改 (線程安全)
- final修飾引用類型變量:初始化之后不能再修改其引用,但可以修改值 (線程不安全)
- 使用JDK / Guava中提供的工具類創建不可變對象
- Collections.unmodifiableXXX 示例 (線程安全)
- Guava ImmutableXXX 示例 (線程安全)
- 代碼
腦圖
四個線程安全策略
線程限制
一個被線程限制的對象,由線程獨占,并且只能被占有它的線程修改
共享只讀
一個共享只讀的對象,在沒有額外同步的情況下,可以被多個線程并發訪問,但是任何線程都不能修改它
線程安全對象
一個線程安全的對象或者容器,在內部通過同步機制來保證線程安全,所以其他線程無需額外的同步就可以通過公共接口隨意訪問它
被守護對象
被守護對象只能通過獲取特定的鎖來訪問
不可變對象定義
在Java中,有一種對象發布了就是安全的,被稱之為不可變對象。
不可變對象可以在多線程中可以保證線程安全
不可變對象需要滿足的條件
- 對象創建以后其狀態就不能修改
- 對象所有域都是final類型
- 對象是正確創建的(在對象創建期間,this引用沒有逸出)
如何創建不可變對象
-
將類聲明成final類型,使其不可以被繼承
-
將所有的成員設置成私有的,使其他的類和對象不能直接訪問這些成員
-
對變量不提供set方法
-
將所有可變的成員聲明為final,這樣只能對他們賦值一次
-
通過構造器初始化所有成員,進行深度拷貝
-
在get方法中,不直接返回對象本身,而是克隆對象,返回對象的拷貝
提到不可變的對象就不得不說一下final關鍵字,該關鍵字可以修飾類、方法、變量:
使用final關鍵字定義不可變對象
final關鍵字可以修飾類、方法、變量
- 修飾類:不能被繼承(final類中的所有方法都會被隱式的聲明為final方法)
- 修飾方法:
1、鎖定方法不被繼承類修改;
2、可提升效率(private方法被隱式修飾為final方法)
- 修飾變量:
基本數據類型變量: 初始化之后不能修改
引用類型變量: 初始化之后不能再修改其引用
- 修飾方法參數:同修飾變量
修飾變量示例
final修飾基本數據類型及String: 初始化之后不能修改 (線程安全)
可知:編譯報錯,被final修飾后,基本類型和String的變量無法被修改
final修飾引用類型變量:初始化之后不能再修改其引用,但可以修改值 (線程不安全)
package com.artisan.example.immutable;import java.util.ArrayList; import java.util.List; import java.util.Map;import com.google.common.collect.Maps;import lombok.extern.slf4j.Slf4j;@Slf4j public class FinalDemo {// 基本數據類型 int 使用final修飾 驗證被final修飾的基本數據類型無法改變private final static int num = 1;// String類型 使用final修飾 驗證被final修飾的基本數據類型無法改變private final static String name = "小工匠";// 引用類型 初始化之后不能再修改其引用,但是可修改其中值private final static Map<String, Object> map = Maps.newHashMap();static {map.put("name", "artisan");map.put("age", 20);map.put("sex", "男");}public static void main(String[] args) {// 被final修飾的基本數據類型和String無法改變// 編譯報錯: The final field FinalDemo.num cannot be assigned// num = 2;// 編譯報錯: The final field FinalDemo.name cannot be assigned// name = "artisan";// 引用對象,此引用無法指向別的對象,但可修改該對象的值map.put("name", "小工匠");log.info("name:{}", map.get("name"));// 驗證 方法參數被final修飾的情況List<String> list = new ArrayList<>();list.add("我是小工匠");test2(list);}// final修飾傳遞進來的變量基本類型,不可別改變private void test(final int a) {// 不能修改// 編譯報錯: The final local variable a cannot be assigned. It must be blank and not using a compound assignment// a = 2;log.info("a:{}", a);}// final修飾方法傳遞進來的變量 引用對象,無法指向別的對象,但可修改該對象的值private static void test2(final List<String> list) {// 添加數據list.add("我是artisan");list.forEach(str ->{log.info("數據:{}",str);});}}輸出:
需要我們注意的是,final修飾引用類型時,雖然不能將引用再指向別的對象,但可修改該對象的值。 線程不安全
使用JDK / Guava中提供的工具類創建不可變對象
除了final可以定義不可變對象,java提供的Collections類,也可定義不可變對象。
-
JDK中的 Collections.unmodifiableXXX傳入的對象一經初始化便無法修改,XXX可表示Collection、List、Set、Map等
-
谷歌的Guava中的ImmutableXXX,XXX同樣可表示Collection、List、Set、Map等
Collections.unmodifiableXXX 示例 (線程安全)
執行后結果如下:
由此可見,用Collections.UnmodifiableMap修飾的對象是不可修改的,如果嘗試修改對象的值,在程序運行時會拋出異常。
跟下Collections.UnmodifiableMap的源碼
繼續看下UnmodifiableMap
主要是將一個新的集合的所有更新方法變為拋出異常
Guava ImmutableXXX 示例 (線程安全)
package com.artisan.example.immutable;import com.artisan.anno.ThreadSafe; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet;@ThreadSafe public class GuavaImmutableSetDemo {// 使用Guava中提供的類來定義不可變對象的集合// 不可變listprivate final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);// 不可變的setprivate final static ImmutableSet<Integer> set = ImmutableSet.copyOf(list);// 不可變的map,需要以k/v的形式傳入數據,即奇數位參數為key,偶數位參數為valueprivate final static ImmutableMap<String, String> map = ImmutableMap.of("k1", "v1", "k2","v2");// 通過builder調用鏈的方式構造不可變的mapprivate final static ImmutableMap<String, String> map2 = ImmutableMap.<String, String>builder().put("key1", "value1").put("key2", "value2").put("key3", "value3").build();public static void main(String[] args) {// 修改對象內的數據就會拋出UnsupportedOperationException異常// 不能添加新的元素 ,運行將拋出 java.lang.UnsupportedOperationExceptionlist.add(4);// 不能添加新的元素 ,運行將拋出 java.lang.UnsupportedOperationExceptionset.add(4);// 不能添加新的元素 ,運行將拋出 java.lang.UnsupportedOperationExceptionmap.put("k3", "v3");// 不能添加新的元素 ,運行將拋出 java.lang.UnsupportedOperationExceptionmap2.put("key4", "value4");} }上述代碼是線程安全的,開發時如果我們的對象可以變為不可變對象,我們盡量將對象變為不可變對象,這樣可以避免線程安全問題。
代碼
https://github.com/yangshangwei/ConcurrencyMaster
總結
以上是生活随笔為你收集整理的并发编程-10线程安全策略之不可变对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并发编程-09安全发布对象+单例模式详解
- 下一篇: 并发编程-11线程安全策略之线程封闭