Java併發編程-基礎原理

編程語言 Java 編譯器 CPU java學習與交流 java學習與交流 2017-09-24

併發編的挑戰

上下文切換(並行不一定比串行快)

時間片是CPU分配給各個線程的時間,一般是幾十毫秒。因為時間片非常短,所以CPU通過不停地切換線程執行,達到多個線程同時執行的效果

CPU通過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,可以再加載這個任務的狀態。所以任務從保存到再加載的過程就是一次上下文切換

減少上下文切換的方法有無鎖併發編程、CAS算法、使用最少線程和使用協程

無鎖併發編程:如將數據的ID按照Hash算法取模分段,不同的線程處理不同的段的數據

CAS算法:Java的Atomic包使用CAS算法來更新數據,而不需要加鎖

使用最少線程:避免創建不需要的線程

協程:在單線程裡實現多任務的調度,並在單線程裡維持多個任務間的切換

死鎖

鎖是個非常有用的工具,運用場景非常多。但同時它也會帶來一些困擾,那就是可能會引起死鎖,一旦產生死鎖,就會造成系統功能不可用。

避免死鎖的常用方法

  • 避免一個線程同時獲取多個鎖

  • 避免一個線程在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源

  • 嘗試使用定向鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制

  • 對於數據庫鎖,加鎖和解鎖必現在一個數據庫連接池裡,否則會出現解鎖失敗的現象

資源限制

資源限制是指在進行併發編程時,程序的執行速度受限於計算機硬件或軟件資源。硬件資源限制有帶寬的上傳/下載速度、硬盤讀寫速度和CPU處理速度。軟件資源限制有數據庫的連接數和socket連接數等。

對於硬件資源限制,可以考慮使用集群並行執行程序。對於軟件資源限制,可以考慮使用資源池將資源複用。需要根據不同的資源限制調整程序的併發度

Java併發編程-基礎原理

Java內存模型

Java的併發採用的是共享內存模型,Java線程之間的通訊總是隱式進行,整個通信過程對程序員完全透明。Java線程之間的通信由Java內存模型(JMM)控制,JMM決定一個線程對共享變量的寫入核實對另一個線程可見

Java併發編程-基礎原理

  1. 線程對共享變量的所有操作都必須在自己的工作內存中進行,不能直接在主內存中讀寫

  2. 不同線程之間無法直接訪問其他線程工作內存中的變量,線程間變量值的傳遞需要通過主內存來完成。

  3. 線程1對共享變量的修改,要想被線程2及時看到,必須經過如下2個過程:

    把工作內存1中更新過的共享變量刷新到主內存中

    將主內存中最新的共享變量的值更新到工作內存2中

可見性、原子性、重排序

可見性:一個線程對共享變量的修改,更夠及時的被其他線程看到

原子性:即不可再分了,不能分為多步操作。比如賦值或者return。比如"a = 1;"和 "return a;"這樣的操作都具有原子性。類似"a += b"這樣的操作不具有原子性,在某些JVM中"a += b"可能要經過這樣三個步驟:

  1. 取出a和b

  2. 計算a+b

  3. 將計算結果寫入內存

重排序:重排序是指編譯器和處理器為了優化程序性能而對指令序列進行重新排序的一種手段

Volatile

volatile是輕量級的synchronized,它在多處理器開發中保證了共享變量的“可見性”

Volatile實現內存可見性是通過store和load指令完成的;也就是對volatile變量執行寫操作時,會在寫操作後加入一條store指令,即強迫線程將最新的值刷新到主內存中;而在讀操作時,會加入一條load指令,即強迫從主內存中讀入變量的值。但volatile不保證volatile變量的原子性。

Java併發編程-基礎原理

線程執行run()的時候我們需要在線程中不停的做一些事情,比如while循環,那麼這時候該如何停止線程呢?如果線程做的事情不是耗時的,那麼只需要使用一個標誌即可。如果需要退出時,調用setStop()即可。這裡就使用了關鍵字volatile,這個關鍵字的目的是如果修改了isStop的值,那麼在while循環中可以立即讀取到修改後的值

Synchronized

原理

Synchronized在JVM的實現原理,主要是使用了monitorenter和monitorexit指令實現。monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證整個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個monitor與之關聯,當且有一個monitor被持有後,它將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖

synchronized用的鎖是存在Java對象頭裡面的。Java對象頭裡的Mark Word裡默認存儲對象的HashCode、分代年齡和鎖標記位。在運行期間,Mark Word裡存儲的數據會隨著鎖標誌位的變化而變化

鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。JDK 1.6中默認是開啟偏向鎖和輕量級鎖的,我們也可以通過-XX:-UseBiasedLocking來禁用偏向鎖

Java併發編程-基礎原理

用法

synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:

  1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的範圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;

  2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的範圍是整個方法,作用的對象是調用這個方法的對象;

  3. 修改一個靜態的方法,其作用的範圍是整個靜態方法,作用的對象是這個類的所有對象;

  4. 修改一個類,其作用的範圍是synchronized後面括號括起來的部分,作用主的對象是這個類的所有對象。

  • 無論synchronized關鍵字加在方法上還是對象上,如果它作用的對象是非靜態的,則它取得的鎖是對象;如果synchronized作用的對象是一個靜態方法或一個類,則它取得的鎖是對類,該類所有的對象同一把鎖。

  • 每個對象只有一個鎖(lock)與之相關聯,誰拿到這個鎖誰就可以運行它所控制的那段代碼。

  • 實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。

  • synchronized關鍵字不能繼承

Synchronized和Volatile的比較

  1. Synchronized保證內存可見性和操作的原子性

  2. Volatile只能保證內存可見性

  3. Volatile不需要加鎖,比Synchronized更輕量級,並不會阻塞線程(volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。)

  4. volatile標記的變量不會被編譯器優化,而synchronized標記的變量可以被編譯器優化(如編譯器重排序的優化).

  5. volatile是變量修飾符,僅能用於變量,而synchronized是一個方法或塊的修飾符。

Java併發編程-基礎原理

學習過程中遇到什麼問題或者想獲取學習資源的話,歡迎加入Java學習交流群346942462,我們一起學Java!

相關推薦

推薦中...