1. 程式人生 > >(六)、Java 多執行緒——執行緒安全問題

(六)、Java 多執行緒——執行緒安全問題

1、執行緒安全問題的出現

在大多數的多執行緒應用程式中,兩個或者兩個以上的執行緒需要共享對同一資料的存取。這時可能發生多執行緒同時修改共享變數的情況,以在銀行取錢來說,可以分為一下幾個步驟:
1. 輸入卡號和密碼,系統判斷是否匹配並有效
2. 使用者輸入支取金額
3. 系統判斷賬戶可用餘額是否足夠支取
4. 如果滿足支取條件則取款並更新餘額,否則取款失敗
我們使用兩個執行緒來同時模擬取款操作:

public class Account {

        private String acctNo;
        private double balance;

        //getter/setter
//有參構造方法 }
public class GetMoney extends Thread {

    private Account account;
    private double tranAmt; //支取金額

    public GetMoney(String name, Account account, double tranAmt) {
        super(name);
        this.account = account;
        this.tranAmt = tranAmt;
    }

    @Override
    public
void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } if (account.getBalance() >= tranAmt) { //更新餘額 account.setBalance(account.getBalance() - tranAmt); System.out.println(getName() + " 餘額為 : "
+ account.getBalance()); } else { System.out.println(getName() + "賬戶餘額不足,支取失敗!"); } } public static void main(String[] args) { Account account = new Account("3303214000000007654", 1000); new GetMoney("A", account, 400).start(); new GetMoney("B", account, 400).start(); } }

較大概率出現如下輸出結果:

B 餘額為 : 200.0
A 餘額為 : 200.0

執行結果並不是我們希望的:

A 餘額為 : 600.0
b 餘額為 : 200.0

在多執行緒的環境下,如果一個共享資源(取款操作中的賬戶餘額balance)被多個執行緒同時訪問,可能會出現意向不到的情況。特定場景的分析見我的另一篇文章執行緒安全問題

出現這類問題的原因大多數是因為單個操作的顆粒度較小,例如取款中:①、獲取賬戶餘額;②、判斷餘額是否充足;③、更新餘額。這明顯是三個獨立的操作。可以使用同步機制將顆粒度較小的原子操作包裹成顆粒度較大的操作。

2、同步程式碼塊

為了解決執行緒安全問題,Java引入同步程式碼:

synchronized(obj){
    //需要同步的操作
}

任何時刻只能有一個執行緒能夠獲得obj資源並進入同步程式碼塊進行操作,當同步程式碼塊執行完畢後,該執行緒會釋放obj資源。通常使用多執行緒共享的資源充當同步程式碼塊中的“鎖物件”。 例如取錢的例子中應該使用賬戶account充當“鎖物件”。我們將上例修改為:

@Override
    public void run() {
        synchronized (account) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (account.getBalance() >= tranAmt) {
                //更新餘額
                account.setBalance(account.getBalance() - tranAmt);
                System.out.println(getName() + " 餘額為 : " + account.getBalance());
            } else {
                System.out.println(getName() + "賬戶餘額不足,支取失敗!");
            }
        }

    }

使用synchronized將取款的邏輯包裹起來,任何執行緒進入run方法時都會試圖獲取account“鎖物件”,如果某個執行緒獲取到了“鎖物件”,它就可以執行取款操作,其餘執行緒由於不能獲取“鎖物件”,只能等待那個執行緒執行完同步程式碼塊中的程式碼後釋放鎖。
新增同步程式碼塊後程序總會輸出:

A 餘額為 : 600.0
B 餘額為 : 200.0

3、同步方法

同步方法就是使用synchronized關鍵字來修飾某個方法,對於同步方法而言,無需顯示地宣告“鎖物件”,它的“鎖物件”就是物件本身(this)。

package com.xiaopeng.multthread;

public class AccountSyn {

    private String acctNo;
    private double balance;

    public AccountSyn(String acctNo, double balance) {
        this.acctNo = acctNo;
        this.balance = balance;
    }

    public String getAcctNo() {
        return acctNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //同步方法
    public synchronized void draw(double tranAmt) {
        if (balance >= tranAmt) {
            System.out.println(Thread.currentThread().getName() + " 取款 :" + tranAmt + "元");
            balance -= tranAmt;
            System.out.println(Thread.currentThread().getName() + " 餘額為 :" + balance + "元");
        } else {
            System.out.println("賬戶餘額不足");
        }
    }

}

使用同步方法可以實現執行緒安全的類,它們具有以下特徵:

  1. 該類的每個物件都可以被多執行緒訪問
  2. 任意執行緒呼叫該物件的任意方法都可以得到正確的輸出
  3. 執行緒呼叫之後,該物件依舊儲存正常狀態

增加同步的注意點:

  1. 程式碼同步後,同一時間點只能有一個執行緒對其中的任務進行訪問,這會明顯降低程式的執行效率,所以應該只對必要的邏輯進行同步操作。
  2. 如果某段程式碼會執行在單執行緒和多執行緒的環境中,那麼應該提供兩種版本同時保證單執行緒中的效率以及多執行緒中的安全。例如 StringBuffer 保證了多執行緒中的安全性, StringBuilder 保證了單執行緒中的高效率。

相關推薦

Java 執行——執行安全問題

1、執行緒安全問題的出現 在大多數的多執行緒應用程式中,兩個或者兩個以上的執行緒需要共享對同一資料的存取。這時可能發生多執行緒同時修改共享變數的情況,以在銀行取錢來說,可以分為一下幾個步驟: 1. 輸入卡號和密碼,系統判斷是否匹配並有效 2. 使用者輸入支

Java 執行Java記憶體模型

1. 併發程式設計的兩個問題 在併發程式設計中, 需要處理兩個關鍵問題: 執行緒之間如何通訊及執行緒之間如何同步 通訊指的是執行緒之間是以何種機制來交換資訊, 在指令式程式設計中, 執行緒之間的通訊機制有兩種:共享記憶體和訊息傳遞。在共享記憶體的模型中, 執行緒之間共享程式的公共狀態, 通過讀寫記憶體中的

定製併發類自定義在計劃的執行池內執行的任務

宣告:本文是《 Java 7 Concurrency Cookbook 》的第七章, 作者: Javier Fernández González 譯者:鄭玉婷 自定義在計劃的執行緒池內執行的任務 計劃的執行緒池是 Executor 框架的基本執行緒池的擴充套件,允許你定製一個計劃來執行一段時

理解JVMJava記憶體模型與執行

Java記憶體模型 JMM(Java Memory Model)是JVM定義的記憶體模型,用來遮蔽各種硬體和作業系統的記憶體訪問差異。 * 主記憶體:所有的變數都儲存在主記憶體(Main Memory,類比實體記憶體)中。 * 工作記憶體:每條執行緒有自己

Java高併發程式設計學習筆記Java記憶體模型和執行安全

文章目錄 原子性 有序性 可見性 – 編譯器優化 – 硬體優化(如寫吸收,批操作) Java虛擬機器層面的可見性 Happen-Before規則(先行發生) 程式順序原則: volat

Hibernate實現一對對一對映關聯關係

一對多、多對一這種關係在現實生活中很多,例如部門與員工的關係,學校裡班級與學生的關係... 那麼在具體的系統實現中如果i實現這種關聯關係呢?這裡以部門和員工的關係為例。 部門實體類 package test.hibernate.hbmOneToMany; import

Go語言開發Go語言閉包

技術 iad 調用 導致 nil \n 整體 不支持 變化 Go語言開發(六)、Go語言閉包 一、函數式編程 1、函數式編程簡介 函數式編程是一種編程模型,將計算機運算看作是數學中函數的計算,並且避免了狀態以及變量的概念。在面向對象思想產生前,函數式編程已經有數十年的歷史。

SparkSpark任務提交方式和執行流程

sla handles 解析 nod 就會 clust 它的 管理機 nag 一、Spark中的基本概念 (1)Application:表示你的應用程序 (2)Driver:表示main()函數,創建SparkContext。由SparkContext負責與Cluste

Redis學習JAVA操作Redis

enc main package 服務 clas redis學習 static out edi Redis學習(二)、JAVA操作Redis 一、測試連接Redis是否連通 1、導包 <dependency> <groupId>redis.c

MyBatis學習總結---使用log4j2將sql語句執行記錄輸出控制檯和檔案中

  在上一篇部落格中我簡單的介紹了在MyBatis中如何使用日誌,並給出了一個在MyBatis中使用log4j的示例。    MyBatis中日誌的使用及使用log4j示例   下面介紹在MyBatis中如何使用log4j2將sql語句執行記錄輸出控制

jmeter教程java請求

我們傳送http請求或是其它請求,就是在執行一段程式碼,只不過,執行的程式碼,佈署在伺服器上。而java請求,也是在執行一段程式碼,只不過程式碼佈署在本地,需要我們自己實現程式碼的邏輯。利用java請求,可以很方便地實現你想要的需求,當然,前提是你要會java。先看看java請求的具體實現吧,開啟e

c++型的實現過程

一、多型的實現機制           當一個類 A 中有虛擬函式時,A 這個類會自動生成一張虛擬函式表記錄該 A 類所擁有的虛擬函式,並用一個指標 __vfptr 指向該表。當 A  類被當作父類讓子類 B 繼承時,顯然子類 B 也會繼承父類 A 的虛擬函式表指標  __

jmeter元件的作用域與執行順序

jmeter是一個開源的效能測試工具,它可以通過滑鼠拖拽來隨意改變元件之間的順序以及元件的父子關係,那麼隨著它們的順序和所在的域不同,它們在執行的時候,也會有很多不同。 jmeter的test plan通過圖形化的方式表達指令碼,域程式碼方式的指令碼不同,圖形方式表達的指令

Redis的AOF持久化---Redis設計與實現讀書筆記

redisServer關於AOF的資料結構 /** *Redis 伺服器類 */ struct redisServer{ ... //AOF快取區 sds aof_buf; ... } 當伺服器執行完一個寫命令後,會一協議格

排序演算法歸併排序

1、二路歸併排序“歸併”即“合併”,是指將兩個或者兩個以上有序表組合成一個有序表。假如待排序表含有 n 個記錄,即可以視為 n 個有序的子表。每個子表長度為1,然後兩兩歸併,得到 n/2 個長度為 2 或者 1 的有序表,然後,再兩兩歸併,。。。。如此重複,直到合併成一個長度

獲取Keystone token的三種方式

讓我們粗略看一下,三種從Keystone獲得token的方式。在嘗試這三種方式之前,你得確保已經裝好Keystone終端。如果還沒有裝好,可以看grizzly安裝版本的Keystone 安裝部分。 Note:這些呼叫都將請求的是Keystone v2版本

視訊圖形影象處理之Opencv技術記錄均衡直方圖

目標 在本教程中,您將學習: 什麼是影象直方圖以及為什麼它有用 理論 什麼是影象直方圖? 它是影象強度分佈的圖形表示。 它量化了所考慮的每個強度值的畫素數。 什麼是直方圖均衡?

k8s微服務框架istio服務視覺化與監控

前言: 承接上文k8s(五)、微服務框架istio流量策略控制 ,測試環境需保留上文中的環境,在本文將重點展示istio微服務呼叫鏈關係展示,以及微服務工作狀態監測,涉及元件有: prometheus \ grafana \ jaeger等 微服務呼

android Service工作原理

android作業系統支援後臺服務,android應用程式和系統中大多都可以看到服務的影子。 一、android的服務分為兩大部分:android應用服務和android系統服務。 二、android應用服務分為本地服務和遠端服務; 三、android系統服務分為java

ZooKeeper自動重連

       在一套分散式的online services系統中,各service通常不會放在一臺伺服器上,而是通過Zookeeper這樣的東西,將自己的service資訊註冊到上面,service的使用者通過Zookeeper來發現各service的資訊,從而可以將req