什麼是原子類
一度認爲原子是不可分割的最小單位,故原子類可以認爲其操作都是不可分割
爲什麼要有原子類?
對多線程訪問同一個變量,我們需要加鎖,而鎖是比較消耗性能的,JDk1.5之後,
新增的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式,
這些類同樣位於JUC包下的atomic包下,發展到JDk1.8,該包下共有17個類,
囊括了原子更新基本類型、原子更新數組、原子更新屬性、原子更新引用
原子更新基本類型
發展至JDk1.8,基本類型原子類有以下幾個:
- AtomicBoolean、AtomicInteger、AtomicLong、DoubleAccumulator、DoubleAdder、
LongAccumulator、LongAdder
大致可以歸爲3類
- AtomicBoolean、AtomicInteger、AtomicLong 元老級的原子更新,方法幾乎一模一樣
- DoubleAdder、LongAdder 對Double、Long的原子更新性能進行優化提升
- DoubleAccumulator、LongAccumulator 支持自定義運算
實例1:
AtomicInteger atomicInteger = new AtomicInteger();
實例2:
//輸入一個數字,如果比上一個輸入的大,則直接返回,如果小,則返回上一個
public static void main(String[] args) {
LongAccumulator longAccumulator = new LongAccumulator((left, right) ->
left * right, 0L
);
longAccumulator.accumulate(3L);
System.out.println(longAccumulator.get());
longAccumulator.accumulate(5L);
System.out.println(longAccumulator.get());
}
原子更新數組類型
- 原子更新數組類型
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
實例:
public static void main(String[] args) {
int[] arr = new int[]{3, 2};
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
System.out.println(atomicIntegerArray.addAndGet(1, 8));
int i = atomicIntegerArray.accumulateAndGet(0, 2, (left, right) ->
left * right / 3
);
System.out.println(i);
}
原子的更新屬性
原子地更新某個類裏的某個字段時,就需要使用原子更新字段類
Atomic包提供了以下4個類進行原子字段更新:
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference、AtomicReferenceFieldUpdater
使用上述類的時候,必須遵循以下原則:
- 字段必須是volatile類型的,在線程之間共享變量時保證立即可見
- 字段的描述類型是與調用者與操作對象字段的關係一致。
- 也就是說調用者能夠直接操作對象字段,那麼就可以反射進行原子操作。
- 對於父類的字段,子類是不能直接操作的,儘管子類可以訪問父類的字段。
- 只能是實例變量,不能是類變量,也就是說不能加static關鍵字。
- 只能是可修改變量,不能使final變量,因爲final的語義就是不可修改。
- 對於AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long類型的字段,不能修改其包裝類型(Integer/Long)。
- 如果要修改包裝類型就需要使用AtomicReferenceFieldUpdater。
實例:
public static void main(String[] args) {
AtomicLongFieldUpdater<Student> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(Student.class, "id");
Student xdclass = new Student(1L, "xdclass");
longFieldUpdater.compareAndSet(xdclass, 1L, 100L);
System.out.println("id="+xdclass.getId());
AtomicReferenceFieldUpdater<Student, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
referenceFieldUpdater.compareAndSet(xdclass, "xdclass", "wiggin");
System.out.println("name="+xdclass.getName());
}
}
class Student{
volatile long id;
volatile String name;
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
原子更新引用
- AtomicReference:用於對引用的原子更新
- AtomicMarkableReference:帶版本戳的原子引用類型,版本戳爲boolean類型。
- AtomicStampedReference:帶版本戳的原子引用類型,版本戳爲int類型。
public static void main(String[] args) {
AtomicReference<Student> studentAtomicReference = new AtomicReference<>();
Student student = new Student(1L, "xdclass");
Student student1 = new Student(2L, "wiggin");
//需要先對studentAtomicReference進行初始化操作
studentAtomicReference.set(student);
studentAtomicReference.compareAndSet(student, student1);
Student student2 = studentAtomicReference.get();
System.out.println(student2.getName());
}
}
class Student{
private long id;
private String name;
public Student(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
目錄:
ThreadPoolExecutor:
其所有的構造函數如下:
corePoolSize 線程池中核心線程的數量
maximumPoolSize 線程池中最大線程數量
keepAliveTime 非核心線程的超時時長,當系統中非核心線程閒置時間超過keepAliveTime之後,則會被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,則該參數也表示核心線程的超時時長
unit 第三個參數的單位,有納秒、微秒、毫秒、秒、分、時、天等
workQueue 線程池中的任務隊列,該隊列主要用來存儲已經被提交但是尚未執行的任務。存儲在這裏的任務是由ThreadPoolExecutor的execute方法提交來的。
threadFactory 爲線程池提供創建新線程的功能,這個我們一般使用默認即可
handler 拒絕策略,當線程無法執行新任務時(一般是由於線程池中的線程數量已經達到最大數或者線程池關閉導致的),默認情況下,當線程池無法處理新線程時,會拋出一個RejectedExecutionException。
FixedThreadPool:
其內部的實現還是ThreadPoolExecutor,而它的特點在沒有非核心線程,只存在覈心線程並且無過期時間
singleThreadExecutor:
與FixedThreadPool類似,但與之不同的地方在於它只有一條核心線程
CachedThreadPool:
內部實現還是TreadPoolExecutor,它的特點是沒有核心線程,只有非核心線程,並且設有非核心線程的過期時間,過期時間是60秒
ScheduledThreadPool:
ScheduledThreadPool是單獨封裝的一個類,其內部不是直接創建了一個ThreadPoolExecutor,而是集成了ThreadPoolExecutor,它的特點是可以延遲執行任務
線程池拒絕策略
- AbortPolicy:該策略直接拋出異常,阻止系統正常工作 - CallerRunsPolicy:只要線程池沒有關閉,該策略直接在調用者線程中,執行當前被丟棄的任務(叫老闆幫你幹活) - DiscardPolicy:直接啥事都不幹,直接把任務丟棄 - DiscardOldestPolicy:丟棄最老的一個請求(任務隊列裏面的第一個),再嘗試提交任務 - 或者自定義實現RejectedExecutionHandler接口shutDown()和shutDownNow()
shutDown():
當調用該方法的的時候,會將線程的狀態設置爲shutdown狀態,並且禁止再向線程池中添加任務,
否則會拋出異常,而正在的執行的線程會繼續執行,並且將隊列弄未執行的任務執行完畢後纔會
退出
shutDownNow():
當調用該方法的的時候,會將線程的狀態設置爲STOP狀態,並且不會再處理隊列中等待執行的任務,
不過會將這一部分任務進行返回,而正在執行的線程是試圖去終止它,而終止的方式調用了interrupt
函數,而interrupt也只是會改變當前這條線程的狀態標記,代碼中如有:
Thread.sleep、Thread.join、Object.wait、LockSupport.park等在檢查到線程的中斷狀態時,
會拋出InterruptedException,同時會清除線程的中斷狀態,如果沒有,並不會中斷線程任務執行,
需要等正在執行的所以任務執行完畢以後,纔會退出
常用的幾種workQueue
ArrayBlockingQueue:
其內部的存儲實現是一個數組,值得一說的是其內部的存和取都是公用一把鎖,其構造函數形參中必須指定容量的大小,另外它的隊列順序是FIFO
linkedBlockingQueue:
其內部的存儲實現是一個鏈表,內部維護了一個Node類,其可以不指定容量大小,默認值大小是Intger.MAX_VALUE,就是2的32次方-1.而其存和取是獨立的兩把安全鎖
PriorityBlockingQueue:
和linkedBlockingQueue類似,與之不同的是,其隊列的順序並不依照於FIFO,而是取決於Comparator,而我們可以自定義實現Comparator,實現自定義排序,從而實現自定義優先級
SynchronousQueue:
其內部並沒有維護一個存儲數據的列表,所以是無法進行遍歷,插入等操作的,其寫入和讀取都是交替完成,當生產者線程想存入數值是需等消費者線程將值取出之後才能寫入,這是SynchronousQueue的特點.
總結:
在實際的使用中,使用的比較多的是linkedBlockingQueue和PriorityBlockingQueue;
使用PriorityBlockingQueue是爲了滿足自定義隊列順序的要求;
而使用linkedBlockingQueue和未選擇相似的ArrayBlockingQueue是因爲linkedBlockingQueue數據的吞吐量會比它大,而比他大的原因在於兩方面:
一方面是數據結構的原因,數組的地址是連續性的在存入和取出的時候,一整個數組數據的存儲位置都需要位移,而鏈表式前後數據都不需要位移只需要修改前後值索引所需的指針指向.
二方面是LinkedBlockingQueue的存入和讀取分別是獨立的兩把鎖,而ArrayBlockingQueue存入和讀取是公用一把鎖,顯然在高併發的時候linkedBlockingQueue的數據流通的通透性更強.
BlockingQueue常用的API:
存入:
add,offer,put
取出:
remove,poll,take
他們都存在着對應關係:
add — remove
offer — poll
put — take
add:向BlockingQueue存入一個值,當隊列未滿可以存入的時候,返回true,當滿了無法存入則拋出異常,其內部底層調用的是offer函數
offer:向BlockingQueue存入一個值,當隊列未滿可以存入的時候,返回true,當滿了無法存入則返回false
put:向BlockingQueue存入一個值,當隊列未滿可以存入的時候,直接存入,而當隊列滿了無法存入的時候則調用此方法的線程被阻斷,直到隊列有空間再繼續
remove:向BlockingQueue取出一個值,當能取出值則取出,當爲空沒有的時候就拋出異常,其內部底層調用的是poll函數
poll:向BlockingQueue取出一個值,當能取出值則取出,返回true當爲空沒有的時候就返回false
take:向BlockingQueue取出一個值,當能取出值則取出,當爲空沒有的時候就調用此方法的線程被阻斷,直到有值可以被取出.