原子類
java.util.concurrent.atomic包:原子類的小工具包,支持在單個變量上解除鎖的線程安全編程
原子變量類相當於一種泛化的 volatile 變量,能夠支持原子的和有條件的讀-改-寫操作。AtomicInteger 表示一個int類型的值,並提供了 get 和 set 方法,這些 Volatile 類型的int變量在讀取和寫入上有着相同的內存語義。它還提供了一個原子的 compareAndSet 方法(如果該方法成功執行,那么將實現與讀取/寫入一個 volatile 變量相同的內存效果),以及原子的添加、遞增和遞減等方法。AtomicInteger 表面上非常像一個擴展的 Counter 類,但在發生競爭的情況下能提供更高的可伸縮性,因為它直接利用了硬件對並發的支持。
悲觀鎖
為什么會有原子類
CAS:Compare and Swap,即比較再交換。
jdk5增加了並發包java.util.concurrent.*,其下面的類使用CAS算法實現了區別於synchronouse同步鎖的一種樂觀鎖。JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這是一種獨占鎖,也是是悲觀鎖。
如果同一個變量要被多個線程訪問,則可以使用該包中的類
AtomicBoolean
AtomicInteger
AtomicLong
AtomicReference
我只說下AtomicInteger,大家了解下。其他的都是類似的。
先看這段代碼:
package com.toov5.thread; import javax.swing.text.AbstractDocument.BranchElement; //兩個線程同時操作一個全局變量 線程安全問題 public class Test01 extends Thread{ //共享全局變量 private static int count = 1; @Override public void run() { while(true){ Integer count = getCount(); if (count==1000) { break; } System.out.println(count); } } public Integer getCount(){ try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return count++; } public static void main(String[] args) { Test01 t1 = new Test01(); Test01 t2 = new Test01(); t1.start(); t2.start(); } }
線程安全問題:
、
解決線程安全問題 可以用synchronize ,原子性 可見性 可重入性
效率低 不能解決重排序
加了之后 還是有這個問題 這是兩個線程 用的this鎖!! 應該用同步代碼塊 但是 Runnable就不一樣的解決方式了
解決!!
synchronize 只有一個線程進去 解決完了 后面的才進來 效率低 阻塞
用樂觀鎖解決: AtomicInteger
線程安全 共享全局變量 做++操作的 底層是個線程安全的++
package com.toov5.thread; import java.util.concurrent.atomic.AtomicInteger; //兩個線程同時操作一個全局變量 線程安全問題 public class Test01 implements Runnable{ //共享全局變量 // private static int count = 1; AtomicInteger atomicInteger = new AtomicInteger(); @Override public void run() { while(true){ Integer count = getCount(); if (count==1000) { break; } System.out.println(count); } } public Integer getCount(){ try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return atomicInteger.incrementAndGet(); //每次做自增 } public static void main(String[] args) { Test01 test01 = new Test01(); Thread t1 = new Thread(test01); Thread t2 = new Thread(test01); t1.start(); t2.start(); } }
線程安全的,但是他底層沒有使用鎖 CAS無鎖機制
do while 做比較 一直比較 類似於自旋鎖 自旋嘛
CAS與Java內存模型(JMM)結合理解
CAS無鎖機制 三個參數:
V :需要更新的變量 主內存中的
E :預期值 本地內存的
N :新值
如果:
V=E 說明沒有被修改過 將V的值設為N
V!=E 說明被修改過 主內存中的值刷新到本地內存,然后進行循環比較,一致了就改為N
CAS無鎖模式
什么是CAS
CAS:Compare and Swap,即比較再交換。
jdk5增加了並發包java.util.concurrent.*,其下面的類使用CAS算法實現了區別於synchronouse同步鎖的一種樂觀鎖。JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這是一種獨占鎖,也是是悲觀鎖。
CAS算法理解
(1)與鎖相比,使用比較交換(下文簡稱CAS)會使程序看起來更加復雜一些。但由於其非阻塞性,它對死鎖問題天生免疫,並且,線程間的相互影響也遠遠比基於鎖的方式要小。更為重要的是,使用無鎖的方式完全沒有鎖競爭帶來的系統開銷,也沒有線程間頻繁調度帶來的開銷,因此,它要比基於鎖的方式擁有更優越的性能。
(2)無鎖的好處:
第一,在高並發的情況下,它比有鎖的程序擁有更好的性能;
第二,它天生就是死鎖免疫的。
就憑借這兩個優勢,就值得我們冒險嘗試使用無鎖的並發。
(3)CAS算法的過程是這樣:它包含三個參數CAS(V,E,N): V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他線程做了更新,則當前線程什么都不做。最后,CAS返回當前V的真實值。
(4)CAS操作是抱着樂觀的態度進行的,它總是認為自己可以成功完成操作。當多個線程同時使用CAS操作一個變量時,只有一個會勝出,並成功更新,其余均會失敗。失敗的線程不會被掛起,僅是被告知失敗,並且允許再次嘗試,當然也允許失敗的線程放棄操作。基於這樣的原理,CAS操作即使沒有鎖,也可以發現其他線程對當前線程的干擾,並進行恰當的處理。
(5)簡單地說,CAS需要你額外給出一個期望值,也就是你認為這個變量現在應該是什么樣子的。如果變量不是你想象的那樣,那說明它已經被別人修改過了。你就重新讀取,再次嘗試修改就好了。
(6)在硬件層面,大部分的現代處理器都已經支持原子化的CAS指令。在JDK 5.0以后,虛擬機便可以使用這個指令來實現並發操作和並發數據結構,並且,這種操作在虛擬機中可以說是無處不在。
CAS(樂觀鎖算法)的基本假設前提
CAS比較與交換的偽代碼可以表示為:
do{
備份舊數據;
基於舊數據構造新數據;
}while(!CAS( 內存地址,備份的舊數據,新數據 ))
就是指當兩者進行比較時,如果相等,則證明共享數據沒有被修改,替換成新值,然后繼續往下運行;如果不相等,說明共享數據已經被修改,放棄已經所做的操作,然后重新執行剛才的操作。容易看出 CAS 操作是基於共享數據不會被修改的假設,采用了類似於數據庫的 commit-retry 的模式。當同步沖突出現的機會很少時,這種假設能帶來較大的性能提升。