java并發編程

jopen 10年前發布 | 33K 次閱讀 java并發 Java開發

鎖的持有者,誰是鎖

    public class Lock (      
       public synchronized  void fun1(){
              //業務運算.       
       };
       public static synchronized void fun2(){
              //業務運算.      
       }
}
Lock a = new Lock();

fun1:鎖是 Lock 對象也就是 a this,持有鎖這調用的線程。
fun2:鎖是 Lock.class,持有鎖這調用的線程。
當線程持有了鎖,當要進入需要相同鎖的地方,可以進入。

 

synchronized 注意地方,缺點:

注意地方:
    鎖是用在多線程并發操作:當線程獲取到了鎖,調用了sleeep(休眠),線程不會釋放資源,釋放鎖,
wait,線程會釋放鎖,當再次醒來后又要重新獲取鎖,需要在同步塊。
notify:喚醒由于該條件等待的線程中的一個線程,需要在同步塊。
notifyAll:喚醒所有。 一般就調用notifyAll:不然可能會造成某些線程假死,點背一直沒有喚醒過他。需要在同步塊。

缺點:
      當并發時候需要超時中斷,不能實現。只能傻等到得到鎖業務計算完畢退出。

顯示鎖:ReentrantLock:

   語義上還有和 synchronized 完全相同,只是更多的功能

寫法:

  lock.lock();// 獲取鎖

  lock.unlock();// 釋放鎖,一定要和數據庫連接一樣,放到 finally 

由于和數據庫連接一樣,增加了危險性。

常用 api 解釋:

     tryLock(long timeout, TimeUnit unit ) // 獲取鎖,不能返回 false 或一個時間后 不能獲取返回。


ReentrantLock的wait,notify, notifyAll:

    和synchronized 的wait,notify,notifyAll對應。
如果使用了ReentrantLock不能使用wait,notify,notifyAll方法。 
//生產者消費者的生產環境,有限的數組,當數組滿了后需要等待,消費者清除了數據后需要喚醒生產者線程。 
ReentrantLock lock = new ReentrantLock();
Condition full = lock.newCondition(); 
public void put(String str){
if(isFull()){//是否已經滿了
     full.await();
          }
} 
public void get(){
      //清除業務 
      full.notifyAll();
} 
覺得更加有針對性的面向對象的編程。 
 

數據庫的類似問題:

    臟讀:第一個事物讀取第二事物正在更新的數據,如果更新語句尚未完成,則第一個事物讀取到的只是一個過程中的數據,而并非真實的結果。oracle的事物默認是:read committed(提交讀), 不會出現該問題
   排他鎖 :當變更數據,獲取排他鎖。
   共享鎖:查詢獲取共享鎖,數據可以被多線程獲取多個共享鎖。獲取了共享鎖,再獲取排他鎖,需要等待共享鎖結束。

 

Java解決數據庫的類似問題
   臟讀:violation 關鍵字,可見性 如64bit的long,double。
   排他鎖 :默認synchronized,ReentrantLock都是排他鎖。
   共享鎖:實現類ReentrantReadWriteLock ,讀寫鎖。場景:如我們的系統緩存常量數據一般都是讀,很少的修改。


java并發編程

并發類介紹-----原子基本變量

   Boolean:AtomicBoolean 
   Long:AtomicLong 
   引用:AtomicReference 
   鏈表中的大量數據需要包裝,使用域的更新,AtomicReferenceFieldUpdater 
案例:系統的訪問次數,你肯定用一個long sum = 0;
sum++方法,
從計算機的原子操作看是有3步 1.取出sum=0 2.加1 sum+1  3.把加的值放回到sum的區域 sum=1;
并發操作要講究原子操作,我們一般使用 隱藏鎖(synchronized)或顯示鎖(lock).


并發類介紹---CAS(compare and  swap)
  偽代碼:
addOne(){
   for(;;){
     int old = 當前的值;
     int new = old+1;
     if(cas(old,new)){
       return;
      }
    }
}

/**
*這一塊是cpu指令實現原子,當替換成功返回true,否則false.
*/
cas(int old,int new){
   if(old==當前的值){//就是剛才的當前值,相同表示沒有變化。
      當前的值=new;
      return true;
   }else{//如果不相同表示已經被修改,那么就返回false,上面函數再調用cas.. 
      return false
   }
}


并發類介紹-----CAS與鎖實現比較
    鎖的基本實現:A 線程獲取鎖,B獲取鎖等待,B釋放鎖喚醒所有等待線程,B獲取鎖
    休眠等待需要操作系統的上下文切換,從用戶態到系統態的切換,比較慢。
    如果用cas的話,直接是jvm計算,當超級大并發,競爭異常激烈時候,cas就不一定比鎖性能更好了,從這些業務算法上看,計算機的科學也是為了解決具體的事情,那些牛人想破腦袋想出來的。

 

并發類介紹-----緩存隊列

   數組(array)如:ArrayList  
   數組鏈相結合產物:HashMap;
   為了并發操作更快速,使用更加簡便設計。并發包java.util.concurrent 
   有限數組(array):ArrayBlockingQueue,
   并發數組鏈map:ConcurrentHashMap。
   當map很大時候,添加修改一次需要花費資源越來越大,可以設置成map中多個map,然后計算hash取模,加鎖只加其中的一個小map。和數據庫的分區類似。分離鎖,只對一塊數據中的一個區鎖定。

并發類介紹----工具類
    信號量,semaphore:如最多5個信號,業務運算時候需要先得到信號(acquire),在運算,結束后再release。有些像連接池一樣,限制的計算量。
    關卡,barrier,實現類:CyclicBarrier,執行完的線程在最后等待,等待最后的線程執行完,然后大家一起結束。
    閉鎖,latch, CountDownLatch:等到所有資源集合完畢,等待的線程才能統一的都運行。

 

并發類介紹----線程池工具類
    當創建很多運行時間很多的線程時候,jvm為分配資源的代價越來越高,線程池和數據庫連接池類似
    Executors 類下的靜態方法,
newFixedThreadPool(int nThreads);--定長線程池。
newCachedThreadPool(); --根據系統需要創建,然后重用等方法。
   緩慢的劣質化:
當用池后,你的線程不會無限的增長導致內存溢出,在你的控制下,你的系統負載很高時,用戶提交的數據在你的jvm中被阻塞,后來又在操作系統層面緩存提交,操作系統不夠后只能在路由器緩存,最后路由器就timeout給用戶。

 

并發測試:
   垃圾回收會影響你的測試報告,禁止測試時候執行垃圾回收:-verbose:gc
   方法首先運行使用解釋字節碼方式執行,足夠頻繁時候會動態編譯, 打印編譯信息,-XX:+PrintCompilation     
   Jdk有client,server 多種運行模式,發布時候肯定運行于server模式下,該模式下更擅長優化死代碼:-server

 

公式定律
   Amdahl定律:當計算資源必須使用串行化占比,然后計算出可提升性能的公式:

   定制java 線程池大小:等待時間(WT)與服務時間(ST)之間的比例。如果我們將這一比例稱之為 WT/ST,那么對于一個具有 N 個處理器的系統,需要設置大約 N*(1+WT/ST) 個線程來保持處理器得到充分利用

 

happen-before :
   編譯器為了提高多線程性能,會對代碼進行重新排序,基于happen-before 規則才能確定代碼執行的先后順序。
   1.單線程規則:同一個線程中,書寫在前面的操作happen-before書寫在后面的操作。這條規則是說,在單線程 中操作間happen-before關系完全是由源代碼的順序決定的。
   2.對鎖的unlock操作happen-before后續的對同一個鎖的lock操作。這里的“后續”指的是時間上的先后關系,unlock操作發 生在退出同步塊之后,lock操作發生在進入同步塊之前。必須對同一個變量的 所有 讀寫同步,才能保證不讀取到陳舊的數據,僅僅同步讀或寫是不夠的 。  
   3.如果操作A happen-before操作B,操作B happen-before操作C,那么操作A happen-before操作C。稱為傳遞規則。
   可參考:http://www.iteye.com/topic/260515 

并發內部隱示鎖:synchronized

特性:可見性:和 violation 一樣,

      原子性:把一些不是原子操作組合成原子操作。

      當一個 final 變量時候不需要做同步 , 但是一個對像需要內部的成員變量是否 final 

     當一個變量創建后要變化,需要在修改和獲取時候都要加鎖。不然遍歷時可能 拋出 ConcurrentModificationException 被變化異常。

       系統運行也不是單個計算機或現在的單核cpu能夠很好解決運行:系統需要 顯示多機多cpu負載計算,為了響應當前的低碳生活,我們需要提高單機的計算能 力,更好的學習設計并發。

  

關鍵字:

    原子操作:原子為不可再分操作。

   Violation :可見關鍵字。

   Synchronized:內部隱示鎖 
   ReentrantLock:顯示鎖 
   ReentrantReadWriteLock:讀寫鎖 
   final:創建后不變

 

jmm(java內存模型):

    java并發編程

 

線程對所有變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞 均需要通過主存完成

 

線程 thread,runable常用方法

    Interrupt():中斷該線程,只是java語法上說要中斷,具體實現需要業務上判斷, 
    interrupted():判斷是否中斷,并且清除中斷狀態。 
    isInterrupted() :判斷是否中斷,不清除中斷狀態。 
    join() :等待該線程終止。 
    yield() :暫停執行當前線程,讓出資源,讓那個jvm的線程排程器 從可執行狀態的線程中重新進行排程。也許該線程立馬就又可以運行。 
    線程狀態:new-》可運行-》排程器調度到運行-》等待,阻塞,睡眠-》運行完畢 

中斷:

    如 sock 通訊, read  write 被阻塞,不好中斷,可通過關閉 sock.close() 實現中斷 ;

 

并發可見關鍵字:violation

      當多線程修改同一個數據時候,由于jmm限制,并不能立馬讓other thread察覺。
     硬件上實現:cpu硬件廠商提供了,各個cpu核心數據同步的關卡或柵欄。Java提供了這樣的關鍵字機制:violation
會主動同步各個工作內存的數據到主內存中.
     volatile字段的寫操作happen-before后續的對同一個字段的讀操作 :詳見組后的happen-before規則。
     如 系統線程:
//表示是否運行
private volatile boolean running = false;
64位的 long,double 讀寫分為2個32位的操作,聲明為violation,jmm會規定為原子操作。是否會在64bit機器有限制

 

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!