日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

[Java 并发编程实战] 设计线程安全的类的三个方式(含代码)

發(fā)布時(shí)間:2024/4/14 java 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [Java 并发编程实战] 设计线程安全的类的三个方式(含代码) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

發(fā)奮忘食,樂(lè)以忘優(yōu),不知老之將至。———《論語(yǔ)》

前面幾篇已經(jīng)介紹了關(guān)于線程安全和同步的相關(guān)知識(shí),那么有了這些概念,我們就可以開(kāi)始著手設(shè)計(jì)線程安全的類(lèi)。本文將介紹構(gòu)建線程安全類(lèi)的幾個(gè)方法,并說(shuō)明他的區(qū)別。

我要講的這幾個(gè)構(gòu)建線程安全類(lèi)的方式是:

  • 實(shí)例封閉。
  • 線程安全性的委托。
  • 現(xiàn)有的線程安全類(lèi)添加功能。
  • 另外,在設(shè)計(jì)線程安全類(lèi)的過(guò)程中,我們需要考慮下面三個(gè)基本要素,遵循這三個(gè)步驟:

    • 找出構(gòu)成對(duì)象狀態(tài)的所有變量。
    • 找出約束狀態(tài)變量的不變性條件。
    • 建立對(duì)象狀態(tài)的并發(fā)訪問(wèn)策略。

    以上,就是這篇文章主要講解的內(nèi)容,下面章節(jié)分三個(gè)構(gòu)建方法逐步展開(kāi)說(shuō)明,逐個(gè)分析,并附上自己測(cè)試過(guò)的實(shí)例代碼,確保這篇文章分享的內(nèi)容是經(jīng)過(guò)驗(yàn)證的。

    實(shí)例封閉

    意思是將數(shù)據(jù)封裝在對(duì)象內(nèi)部,它將數(shù)據(jù)的訪問(wèn)限制在對(duì)象的方法上,從而更容易確保線程在訪問(wèn)數(shù)據(jù)時(shí)總能持有正確的鎖。當(dāng)一個(gè)非線程安全對(duì)象被封裝到另一對(duì)象中時(shí),能夠訪問(wèn)被封裝對(duì)象的所有代碼路徑都是已知的。這和在整個(gè)程序中直接訪問(wèn)非線程對(duì)象相比,更易于對(duì)代碼進(jìn)行分析。下面代碼清單就是一個(gè)實(shí)例封閉的例子:

    1import java.util.ArrayList;
    2
    3//ThreadSafe
    4public class PointList{
    5
    6 //非線程安全對(duì)象 myList
    7 private final ArrayList<SafePoint> myList = new ArrayList<SafePoint>();
    8
    9 //所有訪問(wèn) myList 的方法都是用同步鎖,確保線程安全
    10 public synchronized void addPoint(SafePoint p) {
    11 myList.add(p);
    12 }
    13 //所有訪問(wèn) myList 的方法都是用同步鎖,確保線程安全
    14 public synchronized boolean containsPoint(SafePoint p) {
    15 return myList.contains(p);
    16 }
    17 //所有訪問(wèn) myList 的方法都是用同步鎖,確保線程安全
    18 //發(fā)布SafePoint
    19 public synchronized SafePoint getPoint(int i) {
    20 return myList.get(i);
    21 }
    22
    23 //ThreadSafe(可發(fā)布的可變線程安全對(duì)象)
    24 class SafePoint{
    25 private int x;
    26 private int y;
    27
    28 private SafePoint(int[] a) {this(a[0], a[1]);}
    29
    30 public SafePoint(SafePoint p) {this(p.get());}
    31
    32 public SafePoint(int x, int y) {
    33 this.x = x;
    34 this.y = y;
    35 }
    36 //使用同步鎖,確保線程安全
    37 public synchronized int[] get() {
    38 return new int[] {x, y};
    39 }
    40 //使用同步鎖,確保線程安全
    41 public synchronized void set(int x, int y) {
    42 this.x = x;
    43 this.y = y;
    44 }
    45 }
    46}

    PointList 的狀態(tài)由 ArrayList 來(lái)管理,但是 ArrayList 并非線程安全的。由于 ArrayList 私有并且不會(huì)逸出,因此 ArrayList 被封閉在 PointList 中。唯一能夠訪問(wèn) ArrayList 的路徑都上同步鎖了,也就是說(shuō) ArrayList 的狀態(tài)完全有 PointList 內(nèi)置鎖保護(hù),因而 PointList 是一個(gè)線程安全的類(lèi)。Point 類(lèi)的安全性放到后面討論。

    從這里例子可以看出,實(shí)例封閉可以非常簡(jiǎn)單的構(gòu)建出線程安全的類(lèi)。封閉機(jī)制更易于構(gòu)造線程安全的類(lèi),因?yàn)楫?dāng)封閉類(lèi)的狀態(tài)時(shí),在分析類(lèi)的線程安全性時(shí)就無(wú)需檢查整個(gè)程序。當(dāng)然,如果將一個(gè)本該封閉的對(duì)象發(fā)布出去,那么也會(huì)破壞封閉性。

    線程安全性的委托

    如果類(lèi)中的各個(gè)狀態(tài)已經(jīng)是線程安全的,那么是否需要再增加一個(gè)線程安全層的封裝呢?
    具體問(wèn)題具體分析,這種需要視情況而定。

    1) 如果各個(gè)狀態(tài)變量是相互獨(dú)立的并且互不依賴(lài),并且沒(méi)有復(fù)合操作,那么可以將線程安全性委托給底層的狀態(tài)變量。如將安全性委托給 value:

    1import java.util.concurrent.atomic.AtomicInteger;
    2
    3public class SafeSequene{
    4 private value = new AtomicInteger(0);
    5 //返回一個(gè)唯一的數(shù)值
    6 public synchronized int getNext(){
    7 return value.incrementAndGet();
    8 }
    9}

    2) 如果各個(gè)狀態(tài)變量之間存在依賴(lài)關(guān)系,并且存在復(fù)合操作,那么是非線程安全的。來(lái)看下面一個(gè)例子,NumberRange 這個(gè)類(lèi)的各個(gè)狀態(tài)組成部分都是線程安全的,但是存在狀態(tài)之間的依賴(lài)關(guān)系,并非互相獨(dú)立,所以也是非線程安全的。

    1import java.util.concurrent.atomic.AtomicInteger;
    2
    3public class NumberRange{
    4
    5 //不變性條件:lower <= upper
    6 private final AtomicInteger lower = new AtomicInteger(0);//線程安全類(lèi)
    7 private final AtomicInteger upper = new AtomicInteger(0);//線程安全類(lèi)
    8
    9 private static boolean flag = true;
    10
    11 private static volatile boolean stopAllThread = false; //檢測(cè)到無(wú)效狀態(tài),停止所有線程并輸出,此時(shí)lower > upper
    12
    13 private static int count = 3; //非線程安全,但是不必理會(huì),不影響我們測(cè)試
    14
    15 //檢查然后更新
    16 public void setLower(int i) {
    17 if(i <= upper.get()) { //lower依賴(lài)upper的值,有可能upper的值已經(jīng)失效
    18 lower.set(i);
    19 }
    20 }
    21
    22 //檢查然后更新
    23 public void setUpper(int i) {
    24 if(i >= lower.get()) { //upper依賴(lài)lower的值,有可能lower的值已經(jīng)失效
    25 upper.set(i);
    26 }
    27 }
    28
    29 public static void main(String[] args) {
    30
    31
    32 NumberRange nr = new NumberRange();
    33 while(stopAllThread == false) {
    34 for(int i = 0; i < 10000; i++) {
    35
    36 if(stopAllThread == true)
    37 break;
    38
    39 new Thread(new Runnable() {
    40 @Override
    41 public void run() {
    42
    43 if(stopAllThread == true)
    44 return;
    45
    46 if(flag == true)
    47 {
    48 flag = false;
    49 nr.setLower(count++);
    50 }
    51 else {
    52 flag = true;
    53 nr.setUpper(count);
    54 }
    55 if(nr.lower.get() > nr.upper.get()) //檢測(cè)到無(wú)效狀態(tài),lower > upper
    56 {
    57 stopAllThread = true;
    58 System.out.println("state wrong");//打印錯(cuò)誤信息
    59 System.out.println("lower = " + nr.lower.get() + " upper = " + nr.upper.get());
    60 }
    61 }
    62 }).start();
    63 }
    64 while(Thread.activeCount() > 1);
    65 System.out.println("lower = " + nr.lower.get() + " upper = " + nr.upper.get());
    66 }
    67 }
    68}

    在上面的程序中,并發(fā)的情況下我們可以檢測(cè)到無(wú)效狀態(tài),即 upper 的值大于 lower 的值。這便是不滿(mǎn)足我們的不變性條件,因?yàn)闋顟B(tài)變量 lower 和 upper 不是彼此獨(dú)立的,因此 NumberRange 不能將線程安全委托給他的線程安全狀態(tài)變量。輸出如下:

    這里寫(xiě)圖片描述

    3) 如何安全的發(fā)布底層的狀態(tài)變量?
    如果一個(gè)狀態(tài)變量是線程安全的,并且沒(méi)有任何不變性條件來(lái)約束他的值,在變量操作上也不存在任何不允許的狀態(tài)轉(zhuǎn)換,那么就可以安全的發(fā)布這個(gè)變量。在示例封閉的代碼清單中,SafePoint 是一個(gè)可變的且線程安全的類(lèi),我們可以安全的發(fā)布它。

    現(xiàn)有的線程安全類(lèi)添加功能

    Java 的類(lèi)庫(kù)中,已經(jīng)包含了很多線程安全的基礎(chǔ)模塊。通常,我們可以直接拿來(lái)重用,并不需要重復(fù)造輪子。重用已有的類(lèi)庫(kù),可以有效降低開(kāi)發(fā)的工作量,開(kāi)發(fā)風(fēng)險(xiǎn)以及維護(hù)成本。下面將講解三種方式來(lái)增加新方法,組合方式將是最優(yōu)的方法。我們應(yīng)當(dāng)避免使用前兩種方式,而所用最后一種方式。

    通過(guò)繼承基類(lèi)添加功能(擴(kuò)展類(lèi)方式)

    假設(shè),我們需要對(duì) Vector 擴(kuò)展,添加一個(gè)[若沒(méi)有則添加]的操作。我們想到的最直接的方法應(yīng)該是修改原始類(lèi),但是通常是無(wú)法做到的,因?yàn)槲覀儤O有可能沒(méi)法訪問(wèn)或修改類(lèi)的源代碼。

    現(xiàn)在采用另一種方式,通過(guò)繼承基類(lèi)的方式擴(kuò)展這個(gè)類(lèi)并添加一個(gè)新方法 putIfAbsent。如下所示:

    1import java.util.Vector;
    2//ThreadSafe
    3public class BetterVector<E> extends Vector<E>{
    4 public synchronized boolean putIfAbsent(E x) {
    5 boolean absent = !contains(x);
    6 if(absent)
    7 add(x);
    8 return absent;
    9 }
    10}

    這樣就可以成功添加一個(gè)新的方法。然而,這比直接在基類(lèi)代碼增加新方法更加脆弱,因?yàn)楝F(xiàn)在的同步策略被分布到多個(gè)源碼文件中。如果底層的類(lèi)修改了同步策略并選擇不同的鎖來(lái)保護(hù),那么子類(lèi)將會(huì)失效,不能保證線程安全。

    客戶(hù)端加鎖機(jī)制

    同樣,來(lái)增加一個(gè)新方法 putIfAbsent,請(qǐng)看下面代碼:

    1import java.util.ArrayList;
    2import java.util.Collections;
    3import java.util.List;
    4
    5public class ListHelper<E> {
    6
    7 public List<E> list = Collections.synchronizedList(new ArrayList<>());
    8 //無(wú)效的同步鎖
    9 public synchronized boolean putIfAbsent(E x) {
    10 boolean absent = !list.contains(x);
    11 if(absent)
    12 list.add(x);
    13 return absent;
    14 }
    15}

    這種方式并不能實(shí)現(xiàn)線程安全,它的問(wèn)題在于同步的時(shí)候使用了錯(cuò)誤的鎖。因?yàn)?List 本身用的鎖肯定不是 ListHelper 上的鎖,這意味著 putIfAbsent 相對(duì)于其他 List 的方法來(lái)說(shuō)并不是同步的。所以看起來(lái)同步了實(shí)際上卻沒(méi)有什么卵用。

    要使這個(gè)方法能夠正確同步,必須在客戶(hù)端加鎖。即對(duì)于使用某個(gè)對(duì)象 X 的客戶(hù)端代碼,使用 X 本身用于保護(hù)其狀態(tài)的鎖來(lái)保護(hù)這段客戶(hù)代碼。要使用客戶(hù)端加鎖,你必須知道對(duì)象 X 使用的是哪個(gè)鎖。

    在 Vector 和同步封裝器的文檔中指出,他們通過(guò)使用 Vector 或封裝器容器的內(nèi)置鎖來(lái)支持客戶(hù)端加鎖。上面代碼可以改成如下:

    1import java.util.ArrayList;
    2import java.util.Collections;
    3import java.util.List;
    4
    5public class ListHelper<E> {
    6
    7 public List<E> list = Collections.synchronizedList(new ArrayList<>());
    8
    9 public boolean putIfAbsent(E x) {
    10 synchronized(list) {//客戶(hù)端加鎖
    11 boolean absent = !list.contains(x);
    12 if(absent)
    13 list.add(x);
    14 return absent;
    15 }
    16 }
    17}

    客戶(hù)端加鎖方式是很脆弱的加鎖方式,意味他將類(lèi) C 的加鎖代碼放到與 C 完全無(wú)關(guān)的其他類(lèi)中。所以在使用客戶(hù)端加鎖時(shí),需要特別小心。

    客戶(hù)端加鎖機(jī)制和擴(kuò)展類(lèi)機(jī)制有許多共同點(diǎn),二者都是講派生類(lèi)的行為與基類(lèi)的實(shí)現(xiàn)耦合在一起,會(huì)破壞實(shí)現(xiàn)的封裝性和同步策略的封裝性。

    組合

    相比前面兩種機(jī)制,這是一種更好的方法。如下所示,ImprovedList 將 List 的操作委托給底層的 List 對(duì)象,然后自己繼承 List 接口的所有方法并對(duì)他們加上同步鎖。

    1import java.util.Collection;
    2import java.util.Iterator;
    3import java.util.List;
    4import java.util.ListIterator;
    5
    6public class ImprovedList<T> implements List<T>{
    7
    8 private final List<T> list;
    9
    10 public ImprovedList(List<T> list) {
    11 this.list = list;
    12 }
    13
    14 //同步方法
    15 public synchronized boolean putIfAbsent(T x) {
    16 boolean contains = list.contains(x);
    17 if(!contains)
    18 list.add(x);
    19 return contains;
    20 }
    21
    22 @Override
    23 public synchronized boolean add(T arg0) {
    24 list.add(arg0);
    25 return false;
    26 }
    27
    28 @Override
    29 public synchronized void clear() {
    30 // TODO Auto-generated method stub
    31 list.clear();
    32 }
    33
    34 //按照此同步方式實(shí)現(xiàn)其他方法
    35
    36}

    ImprovedList 增加了一層自身的內(nèi)置鎖,它不用關(guān)心底層的 List 是否線程安全或者底層 List 修改了他自己的加鎖實(shí)現(xiàn),ImprovedList 都能構(gòu)提供一致的加鎖機(jī)制來(lái)實(shí)現(xiàn)線程安全性。當(dāng)然,加多一層鎖會(huì)導(dǎo)致性能損失,但是 ImprovedList 相比前面兩種方式也更加健壯。

    上面就是構(gòu)建安全類(lèi)的所有內(nèi)容,希望對(duì)你有所幫助,謝謝!!

    轉(zhuǎn)載于:https://www.cnblogs.com/seaicelin/p/9148746.html

    總結(jié)

    以上是生活随笔為你收集整理的[Java 并发编程实战] 设计线程安全的类的三个方式(含代码)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。