1. 程式人生 > >Java高併發解決方案(參考文)

Java高併發解決方案(參考文)

對於我們開發的網站,如果網站的訪問量非常大的話,那麼我們就需要考慮相關的併發訪問問題了。而併發問題是絕大部分的程式設計師頭疼的問題,但話又說回來了,既然逃避不掉,那我們就坦然面對吧~今天就讓我們一起來研究一下常見的併發和同步吧。

為了更好的理解併發和同步,我們需要先明白兩個重要的概念:同步和非同步

1、同步和非同步的區別和聯絡

   同步,可以理解為在執行完一個函式或方法之後,一直等待系統返回值或訊息,這時程式是處於阻塞的,只有接收到返回的值或訊息後才往下執行其它的命令。

        非同步,執行完函式或方法後,不必阻塞性地等待返回值或訊息,只需要向系統委託一個非同步過程,那麼當系統接收到返回值或訊息時,系統會自動觸發委託的非同步過程,從而完成一個完整的流程。

        同步在一定程度上可以看做是單執行緒,這個執行緒請求一個方法後就待這個方法給他回覆,否則他不往下執行(死心眼)。

        非同步在一定程度上可以看做是多執行緒的(廢話,一個執行緒怎麼叫非同步),請求一個方法後,就不管了,繼續執行其他的方法。

   同步就是一件事一件事的做。
        非同步就是,做一件事情,不做其他事情。

        例如:吃飯和說話,只能一件事一件事的來,因為只有一張嘴。但吃飯和聽音樂是非同步的,因為,聽音樂並不影響我們吃飯。

        對於Java程式設計師而言,我們會經常聽到同步關鍵字synchronized,假如這個同步的監視物件是類的話,那麼如果當一個物件訪問類裡面的同步方法時,其它的物件如果想要繼續訪問類裡面的這個同步方法的話,就會進入阻塞,只有等前一個物件執行完該同步方法後,當前物件才能夠繼續執行該方法。這就是同步。

        相反,如果方法前沒有同步關鍵字修飾的話,那麼不同的物件可以在同一時間訪問同一個方法,這就是非同步。

補充一下髒資料和不可重複讀的相關概念:

髒資料

  髒讀就是指當一個事務正在訪問資料,並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個數據。因為這個資料是還沒有提交的資料,那麼另外一個事務讀到的這個資料是髒資料(Dirty Data),依據髒資料所做的操作可能是不正確的。

不可重複讀

  在一個事務內,多次讀同一資料。在這個事務還沒有結束時,另外一個事務也訪問該資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改,那麼第一個事務兩次讀到的資料可能是不一樣的。這樣就發生了在一個事務內兩次讀到的資料是不一樣的,因此稱為是不可重複讀

2、如何處理併發和同步

今天講的如何處理併發和同步問題主要是通過鎖機制。

我們需要明白,鎖機制有兩個層面。

       一種是程式碼層次上的,如java中的同步鎖,典型的就是同步關鍵字synchronized,這裡我不再做過多的講解,感興趣的可以參考:http://www.cnblogs.com/xiohao/p/4151408.html

       另外一種是資料庫層次上的,比較典型的就是悲觀鎖和樂觀鎖。這裡我們重點講解的就是悲觀鎖(傳統的物理鎖)和樂觀鎖。

悲觀鎖(Pessimistic Locking) 

       悲觀鎖,正如其名,它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度。因此,在整個資料處理過程中,將資料處於鎖定狀態。

       悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性。否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。 

一個典型的依賴資料庫的悲觀鎖呼叫: 

select * from account where name=”Erica” for update

這條 sql 語句鎖定了 account 表中所有符合檢索條件( name=”Erica” )的記錄。

本次事務提交之前(事務提交時會釋放事務過程中的鎖),外界無法修改這些記錄。 
Hibernate 的悲觀鎖,也是基於資料庫的鎖機制實現。 
下面的程式碼實現了對查詢記錄的加鎖:

String hqlStr ="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); // 加鎖
List userList = query.list();// 執行查詢,獲取資料

query.setLockMode對查詢語句中,特定別名所對應的記錄進行加鎖(我們為 TUser 類指定了一個別名 “user” ),這裡也就是對返回的所有 user 記錄進行加鎖。

觀察執行期 Hibernate 生成的 SQL 語句: 

select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update

這裡 Hibernate 通過使用資料庫的 for update 子句實現了悲觀鎖機制。 
Hibernate 的加鎖模式有: 
      Ø LockMode.NONE : 無鎖機制。 
      Ø LockMode.WRITE : Hibernate 在 Insert 和 Update 記錄的時候會自動獲取
      Ø LockMode.READ : Hibernate 在讀取記錄的時候會自動獲取。 


以上這三種鎖機制一般由 Hibernate 內部使用,如 Hibernate 為了保證 Update過程中物件不會被外界修改,會在 save 方法實現中自動為目標物件加上 WRITE 鎖。 
      Ø LockMode.UPGRADE :利用資料庫的 for update 子句加鎖。 
      Ø LockMode. UPGRADE_NOWAIT : Oracle 的特定實現,利用 Oracle 的 for update nowait 子句實現加鎖。 


上面這兩種鎖機制是我們在應用層較為常用的,加鎖一般通過以下方法實現:

Criteria.setLockMode
Query.setLockMode
Session.lock

注意,只有在查詢開始之前(也就是 Hiberate 生成 SQL 之前)設定加鎖,才會真正通過資料庫的鎖機制進行加鎖處理。否則,資料已經通過不包含 for update子句的 Select SQL 載入進來,所謂資料庫加鎖也就無從談起。

      為了更好的理解select... for update的鎖表的過程,本人將要以mysql為例,進行相應的講解。

1、要測試鎖定的狀況,可以利用MySQL的Command Mode ,開二個視窗來做測試。

表的基本結構如下:

表中內容如下:

開啟兩個測試視窗,在其中一個視窗執行select * from ta for update0。然後在另外一個視窗執行update操作如下圖:

等到一個視窗commit後的圖片如下:

到這裡,悲觀鎖機制你應該瞭解一些了吧~

需要注意的是for update要放到mysql的事務中,即begin和commit中,否則不起作用。

至於是鎖住整個表還是鎖住選中的行,請參考:

http://www.cnblogs.com/xiohao/p/4385768.html

至於hibernate中的悲觀鎖,使用起來比較簡單,這裡就不寫demo了~感興趣的自己查一下就ok了~、

樂觀鎖(Optimistic Locking)   
         相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。悲觀鎖大多數情況下依靠資料庫的鎖機制實現,以保證操作最大程度的獨佔性。但隨之而來的就是資料庫效能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。 

         如一個金融系統,當某個操作員讀取使用者的資料,並在讀出的使用者數據的基礎上進行修改時(如更改使用者帳戶餘額),如果採用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出資料、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),資料庫記錄始終處於加鎖狀態,可以想見,如果面對幾百上千個併發,這樣的情況將導致怎樣的後果。 樂觀鎖機制在一定程度上解決了這個問題。

        樂觀鎖,大多是基於資料版本 (Version )記錄機制實現。何謂資料版本?

        即為資料增加一個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表增加一個 “version” 欄位來實現。 讀取出資料時,將此版本號一同讀出,之後更時,對此版本號加一。此時,將提交資料的版本資料與資料庫表對應記錄的當前版本資訊進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料。

        對於上面修改使用者帳戶資訊的例子而言,假設資料庫中帳戶資訊表中有一個 version 欄位,當前值為 1 ;而當前帳戶餘額欄位( balance )為 $100 。

        1,操作員 A 此時將其讀出( version=1 ),並從其帳戶餘額中扣除 $50( $100-$50 )。 

        2, 在操作員 A 操作的過程中,操作員 B 也讀入此使用者資訊( version=1 ),並 從其帳戶餘額中扣除 $20 ( $100-$20 )。 

        3 ,操作員 A 完成了修改工作,將資料版本號加一( version=2 ),連同帳戶扣除後餘額( balance=$50 ),提交至資料庫更新,此時由於提交資料版本大於資料庫記錄當前版本,資料被更新,資料庫記錄 version 更新為 2 。 

        4 ,操作員 B 完成了操作,也將版本號加一( version=2 )試圖向資料庫提交資料( balance=$80 ),但此時比對資料庫記錄版本時發現,操作員B提交的資料版本號為 2 ,資料庫記錄當前版本也為 2 ,不滿足 “提交版本必須大於記錄當前版本才能執行更新“ 的樂觀鎖策略。因此,操作員 B 的提交被駁回。 這樣,就避免了操作員 B 用基於version=1 的舊資料修改的結果覆蓋操作員 A 的操作結果的可能。 

        從上面的例子可以看出,樂觀鎖機制避免了長事務中的資料庫加鎖開銷(操作員 A和操作員 B 操作過程中,都沒有對資料庫資料加鎖),大大提升了大併發量下的系統整體效能表現。 需要注意的是,樂觀鎖機制往往基於系統中的資料儲存邏輯,因此也具備一定的侷限性。

        如在上面例子中,由於樂觀鎖機制是在我們的系統中實現,來自外部系統的使用者餘額更新操作不受我們系統的控制,因此可能會造成髒資料被更新到資料庫中。在系統設計階段,我們應該充分考慮到這些情況出現的可能性,並進行相應調整(如將樂觀鎖策略在資料庫儲存過程中實現,對外只開放基於此儲存過程的資料更新途徑,而不是將資料庫表直接對外公開)。 Hibernate 在其資料訪問引擎中內建了樂觀鎖實現。如果不用考慮外部系統對數 據庫的更新操作,利用 Hibernate 提供的透明化樂觀鎖實現,將大大提升我們的生產力。

User.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
       "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
       "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">


<hibernate-mapping package="com.xiaohao.test">

   <class name="User"  table="user" optimistic-lock="version" >
             <id name="id">
           <generator class="native" />
       </id>
       <!--version標籤必須跟在id標籤後面-->
       <version column="version" name="version"  />
       <property name="userName"/>
       <property name="password"/>
               
   </class>
   

</hibernate-mapping>

注意 version 節點必須出現在 ID 節點之後。 
這裡我們聲明瞭一個 version 屬性,用於存放使用者的版本資訊,儲存在 User 表的version中 。
optimistic-lock 屬性有如下可選取值: 
Ø none
無樂觀鎖 
Ø version
通過版本機制實現樂觀鎖 
Ø dirty
通過檢查發生變動過的屬性實現樂觀鎖 
Ø all
通過檢查所有屬性實現樂觀鎖 


        其中通過 version 實現的樂觀鎖機制是 Hibernate 官方推薦的樂觀鎖實現,同時也 
是 Hibernate 中,目前唯一在資料物件脫離 Session 發生修改的情況下依然有效的鎖機 
制。因此,一般情況下,我們都選擇 version 方式作為 Hibernate 樂觀鎖實現機制。

2 . 配置檔案hibernate.cfg.xml和UserTest測試類

hibernate.cfg.xml

<!DOCTYPE hibernate-configuration PUBLIC
       "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
       "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">


<hibernate-configuration>
<session-factory>

   <!-- 指定資料庫方言 如果使用jbpm的話,資料庫方言只能是InnoDB-->
   <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
   <!-- 根據需要自動建立資料表 -->
   <property name="hbm2ddl.auto">update</property>
   <!-- 顯示Hibernate持久化操作所生成的SQL -->
   <property name="show_sql">true</property>
   <!-- 將SQL指令碼進行格式化後再輸出 -->
   <property name="format_sql">false</property>
   <property name="current_session_context_class">thread</property>


   <!-- 匯入對映配置 -->
   <property name="connection.url">jdbc:mysql:///user</property>
   <property name="connection.username">root</

相關推薦

Java併發解決方案參考

對於我們開發的網站,如果網站的訪問量非常大的話,那麼我們就需要考慮相關的併發訪問問題了。而併發問題是絕大部分的程式設計師頭疼的問題,但話又說回來了,既然逃避不掉,那我們就坦然面對吧~今天就讓我們一起來研究一下常見的併發和同步吧。 為了更好的理解併發和同步,我們需要先明白兩個重要的概念:同步和

Java 併發解決方案電商的秒殺和搶購

電商的秒殺和搶購,對我們來說,都不是一個陌生的東西。然而,從技術的角度來說,這對於Web系統是一個巨大的考驗。當一個Web系統,在

併發解決方案負載均衡

1,什麼是負載均衡? 當一臺伺服器的效能達到極限時,我們可以使用伺服器叢集來提高網站的整體效能。那麼,在伺服器叢集中,需要有一臺伺服器充當排程者的角色,使用者的所有請求都會首先由它接收,排程者再根據每臺伺服器的負載情況將請求分配給某一臺後端伺服器去處理。 那麼在這個過程中,排程者如何合理分配

java系統併發解決方案轉載

package com.jb.y2t034.thefifth.web.servlet;   import java.io.ByteArrayOutputStream;   import java.io.FileOutputStream;   import java.io.IOException;   impo

Java併發解決方案之非同步處理

(() -> { // 請求1 CompletableFuture<List<Integer>> completionStage1 = CompletableFuture.supplyAsync(() -> { //

Java併發解決方案

Java高併發,如何解決,什麼方式解決 對於我們開發的網站,如果網站的訪問量非常大的話,那麼我們就需要考慮相關的併發訪問問題了。而併發問題是絕大部分的程式設計師頭疼的問題, 但話又說回來了,既然逃避不掉,那我們就坦然面對吧~今天就讓我們一起來研究一下常見的併

併發程式設計與併發解決方案學習Java 記憶體模型

JMM(Java Memory Model)    JMM是一種規範,規範了Java虛擬機器與計算機記憶體是如何協同工作的,規定了一個執行緒如何和何時可以看到其他執行緒修改過的共享變數的值,以及在必須的時候如果同步的訪問共享變數。棧    棧的優勢:存取速度比堆要快,僅次於計

長文慎入-探索Java併發程式設計與併發解決方案[轉]

轉自:https://yq.aliyun.com/articles/636038 所有示例程式碼,請見/下載於https://github.com/Wasabi1234/concurrency   高併發處理的思路及手段    

java系統併發解決方案之圖片伺服器分離

說明一下: 1、圖片服務通過lvs作為入口,處理能力上還是有保障的。 2、利用nginx直接對外服務,不必用squid。 3、圖中的紅線是指主nginx會將/2006和/2007年的圖片分別代理到兩臺存檔伺服器,如果發現主nginx的cpu佔用比較大,那麼可以考慮使用nginx的proxy_store將圖片存

Java併發程式設計與併發解決方案解析

本文轉載自:Java併發程式設計與高併發解決方案解析 現在在各大網際網路公司中,隨著日益增長的網際網路服務需求,高併發處理已經是一個非常常見的問題,在這篇文章裡面我們重點討論兩個方面的問題,一是併發程式設計,二是高併發解決方案。 文章中的程式碼實現詳見 專案 Git

Java分散式系統併發解決方案

對於我們開發的網站,如果網站的訪問量非常大的話,那麼我們就需要考慮相關的併發訪問問題了。而併發問題是絕大部分的程式設計師頭疼的問題, 但話又說回來了,既然逃避不掉,那我們就坦然面對吧~今天就讓我們一起來研究一下常見的併發和同步吧。 為了更好的理解併發和同步,我們需要先明白兩個重要的概念:同步和非同步    1

併發程式設計與併發解決方案學習併發併發基本概念

一、概念    併發:同時擁有兩個或兩個以上執行緒,如果程式在單核處理器上執行,多個執行緒將替換地換入或者換出記憶體,這些執行緒是同時"存在"的,每個執行緒都處於執行過程中的某個狀態,如果執行在多核處理

NopCommerce 事務解決方案測試

spa nop 模擬 comm nbsp 接口設計 opc 實體 com 首先我們先看數據庫中的數據表(Forums_Group)表的數據 然後我們開始測試,首先不用事務插入,測試代碼: 測試結果: 表數據: 接下來我們模擬一個錯誤,依然不用事

大規模分散式應用之海量資料和併發解決方案總結視訊教程網盤

大規模分散式應用之海量資料和高併發解決方案總結視訊教程網盤 39套Java架構師,高併發,高效能,高可用,分散式,叢集,電商,快取,微服務,微信支付寶支付,公眾號開發,java8新特性,P2P金融專案,程式設計,功能設計,資料庫設計,第三方支付,web安全,效能調優,設計模式,資料結構,併發程式

java遇到的一些問題及解決方案持續更新

問題1:  錯誤:編碼GBK的不可對映字元 解決辦法:(修改編碼) 輸入javac  -encoding utf-8  檔名.java  2、問題public static void main(String[] args)解釋 這裡要

併發解決方案 -負載均衡

上一篇文章說過會轉載一篇負載均衡的介紹方面的文章,就是下面這個了~~~ 什麼是負載均衡? 當一臺伺服器的效能達到極限時,我們可以使用伺服器叢集來提高網站的整體效能。那麼,在伺服器叢集中,需要有一臺伺服器充當排程者的角色,使用者的所有請求都會首先由它接收,排程者再根據每臺伺服器的負載情

jmeter併發設計方案

高併發設計方案二(秒殺架構) 優化方向: (1)將請求儘量攔截在系統上游(不要讓鎖衝突落到資料庫上去)。傳統秒殺系統之所以掛,請求都壓倒了後端資料層,資料讀寫鎖衝突嚴重,併發高響應慢,幾乎所有請求都超時,流量雖大,下單成功的有效流量甚小。以12306為例,一趟火車其實只有2000張票,200w個人來買,基

大型網站應用之海量資料和併發解決方案總結

一、網站應用背景 開發一個網站的應用程式,當用戶規模比較小的時候,使用簡單的:一臺應用伺服器+一臺資料庫伺服器+一臺檔案伺服器,這樣的話完全可以解決一部分問題,也可以通過堆硬體的方式來提高網站應用的訪問效能,當然,也要考慮成本的問題。 當問題的規模在經濟條件下通過堆硬體的

大規模分散式應用之海量資料和併發解決方案總結

一、網站應用背景 開發一個網站的應用程式,當用戶規模比較小的時候,使用簡單的:一臺應用伺服器+一臺資料庫伺服器+一臺檔案伺服器,這樣的話完全可以解決一部分問題,也可以通過堆硬體的方式來提高網站應用的訪問效能,當然,也要考慮成本的問題。 當問題的規模在經濟條件下通過堆硬體的

併發解決方案之負載均衡

1.什麼是負載均衡?         當一臺伺服器的效能達到極限時,我們可以使用伺服器叢集來提高網站的整體效能。那麼,在伺服器叢集中,需要有一臺伺服器充當排程者的角色,使用者的所有請求都會首先由它接收,排程者再根據每