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 ,讀寫鎖。場景:如我們的系統緩存常量數據一般都是讀,很少的修改。
并發類介紹-----原子基本變量
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內存模型):
線程對所有變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞 均需要通過主存完成
線程 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機器有限制