線程安全性:
定義:當(dāng)多個線程訪問某個類時,不管允許時環(huán)境采用何種調(diào)度方式或者這些進(jìn)程將如何交替執(zhí)行眨攘,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這些類都能表現(xiàn)出正確的行為,那么就稱為這個類是線程安全的.
- 原子性:提供互斥訪問递惋,同一時刻只能有一個線程來對它進(jìn)行操作。
- 可見性:一個線程對主內(nèi)存的修改可以及時的被其他線程觀察到雹顺。
- 有序性:一個線程觀察其他線程中的指令執(zhí)行順序丹墨,由于指令重排序的存在,該觀察結(jié)果一般雜亂無序嬉愧。
原子性
Atomic包
-
AtomicXXX:CAS贩挣,Unsafe.compareAndSwapInt
Atomic
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
// 主要是調(diào)用了unsafe的方法
// private static final Unsafe unsafe = Unsafe.getUnsafe();
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
/**
* 獲取底層當(dāng)前的值并且+1
* @param var1 需要操作的AtomicInteger 對象
* @param var2 當(dāng)前的值
* @param var4 要增加的值
*/
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 獲取底層的該對象當(dāng)前的值
var5 = this.getIntVolatile(var1, var2);
// 獲取完底層的值和自增操作之間,可能系統(tǒng)的值已經(jīng)又被其他線程改變了
//如果又被改變了没酣,則重新計算系統(tǒng)底層的值王财,并重新執(zhí)行本地方法
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
/**
* 本地的CAS方法核心
* @param var1 需要操作的AtomicInteger 對象
* @param var2 當(dāng)前本地變量中的的值
* @param var4 當(dāng)前系統(tǒng)從底層傳來的值
* @param var5 要更新后的值
* @Return 如果當(dāng)前本地變量的值(var2)與底層的值(var4)不等,則返回false裕便,否則更新為var5的值并返回True
*/
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
- AtomicLong绒净、LongAdder
LongAdder的設(shè)計思想:核心是將熱點(diǎn)數(shù)據(jù)分離,將內(nèi)部數(shù)據(jù)value分成一個數(shù)組偿衰,每個線程訪問時挂疆,通過hash等算法映射到其中一個數(shù)字進(jìn)行技術(shù)改览,而最終計數(shù)結(jié)果為這個數(shù)組的求和累加,其中熱點(diǎn)數(shù)據(jù)value會被分離成多個熱點(diǎn)單元的數(shù)據(jù)cell缤言,每個cell獨(dú)自維護(hù)內(nèi)部的值宝当,當(dāng)前value的實(shí)際值由所有的cell累積合成,從而使熱點(diǎn)進(jìn)行了有效的分離胆萧,提高了并行度.
LongAdder 在低并發(fā)的時候通過直接操作base庆揩,可以很好的保證和Atomic的性能基本一致,在高并發(fā)的場景跌穗,通過熱點(diǎn)分區(qū)來提高并行度
缺點(diǎn):在統(tǒng)計的時候如果有并發(fā)更新订晌,可能會導(dǎo)致結(jié)果有些誤差
- AtomicReference、AtomicReferenceFieldUpdater
AtomicReference: 用法同AtomicInteger一樣蚌吸,但是可以放各種對象
package com.mmall.example.atomic;
import com.mmall.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
/**
* 線程安全的 --> AtomicReference
* Created by megan on 2018/3/18.
*/
@Slf4j
@ThreadSafe
public class AtomicReferenceExample {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
// 2
count.compareAndSet(0,2);
// no
count.compareAndSet(0,1);
// no
count.compareAndSet(1,3);
// 4
count.compareAndSet(2,4);
// no
count.compareAndSet(3,5);
log.info("count:{}",count);
}
}
AtomicReferenceFieldUpdater:
package com.mmall.example.atomic;
import com.mmall.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 線程安全的 --> AtomicReferenceFieldUpdater
* Created by megan on 2018/3/18.
*/
@Slf4j
@ThreadSafe
public class AtomicReferenceFieldUpdaterExample {
private static AtomicIntegerFieldUpdater<AtomicReferenceFieldUpdaterExample> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterExample.class, "count");
@Getter
private volatile int count = 100;
public static void main(String[] args) {
AtomicReferenceFieldUpdaterExample example = new AtomicReferenceFieldUpdaterExample();
if(updater.compareAndSet(example,100,200)){
log.info("update success 1 , {}",example.getCount());
}
if(updater.compareAndSet(example,100,200)){
log.info("update success 2 , {}",example.getCount());
}else{
log.info("update failed ,{}",example.getCount());
}
}
}
- AtomicStampReference:CAS的ABA問題
ABA問題:在CAS操作的時候锈拨,其他線程將變量的值A(chǔ)改成了B由改成了A,本線程使用期望值A(chǔ)與當(dāng)前變量進(jìn)行比較的時候套利,發(fā)現(xiàn)A變量沒有變推励,于是CAS就將A值進(jìn)行了交換操作,這個時候?qū)嶋H上A值已經(jīng)被其他線程改變過肉迫,這與設(shè)計思想是不符合的
解決思路:每次變量更新的時候验辞,把變量的版本號加一,這樣只要變量被某一個線程修改過喊衫,該變量版本號就會發(fā)生遞增操作跌造,從而解決了ABA變化
- AtomicBoolean
package com.mmall.example.atomic;
import com.mmall.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 線程安全的 --> AtomicBoolean
* 5000次請求,test方法只執(zhí)行一次
* Created by megan on 2018/3/18.
*/
@Slf4j
@ThreadSafe
public class AtomicBooleanExample {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
/** 請求數(shù) **/
public static int clientTotal = 5000;
/** 同時并發(fā)執(zhí)行線程數(shù) **/
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
//定義一個線程池
ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(), r -> new Thread(r,"測試線程AtomicBoolean"));
// 信號量族购,閉鎖
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
// 模擬并發(fā)請求
for(int i = 0; i < clientTotal; i ++){
executorService.execute(() -> {
try {
// 請求一個信號壳贪,如果信號量小于clientTotal,則阻塞
semaphore.acquire();
test();
// 釋放一個信號
semaphore.release();
} catch (InterruptedException e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
// 阻塞直到countDown 的次數(shù)為threadTotal
countDownLatch.await();
// 關(guān)閉線程池
executorService.shutdown();
log.info("isHappened:{}",isHappened);
}
private static void test(){
if(isHappened.compareAndSet(false,true)){
log.info("execute");
}
}
}
原子性-鎖
- synchronized:依賴JVM (主要依賴JVM實(shí)現(xiàn)鎖寝杖,因此在這個關(guān)鍵字作用對象的作用范圍內(nèi)违施,都是同一時刻只能有一個線程進(jìn)行操作的)
1、修飾代碼塊:大括號括起來的代碼瑟幕,作用于調(diào)用的對象
2磕蒲、修飾方法,作用于調(diào)用的對象
3只盹、修飾靜態(tài)方法:整個靜態(tài)方法辣往,作用于所有對象
4、修飾類:括號包起來的部分殖卑,作用于所有對象
package com.mmall.example.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
/**
* Synchronized 鎖
* Created by megan on 2018/3/21.
*/
@Slf4j
public class SynchronizedExample1 {
/**
* 修飾代碼塊,作用范圍為大括號括起來的
*/
public void test1(int j){
synchronized (this){
for(int i = 0,b = 10; i<b ; i++){
log.info("test1 {} --> {}",j,i);
}
}
}
/**
* 修飾整個方法站削,作用范圍是整個方法,作用對象為調(diào)用這個方法的對象
* 若子類繼承父類調(diào)用父類的synchronized方法孵稽,是帶不上synchronized關(guān)鍵字的
* 原因:synchronized 不屬于方法聲明的一部分
* 如果子類也想使用同步需要在方法上聲明
*/
public synchronized void test2(int j){
for(int i = 0,b = 10; i<b ; i++){
log.info("test2 {} --> {}",j,i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
//聲明一個線程池
ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(), r -> new Thread(r,"測試線程(Synchronized1)"));
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example2.test2(2);
});
}
}
package com.mmall.example.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Synchronized 鎖
* Created by megan on 2018/3/21.
*/
@Slf4j
public class SynchronizedExample2 {
/**
* 修飾類
* 作用對象為這個類的所有對象
*/
public static void test1(int j){
synchronized (SynchronizedExample2.class){
for(int i = 0,b = 10; i<b ; i++){
log.info("test1 {} --> {}",j,i);
}
}
}
/**
* 修飾靜態(tài)方法
* 作用對象為這個類的所有對象
*/
public static synchronized void test2(int j){
for(int i = 0,b = 10; i<b ; i++){
log.info("test2 {} --> {}",j,i);
}
}
public static void main(String[] args) {
//聲明一個線程池
ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(), r -> new Thread(r,"測試線程(Synchronized1)"));
executorService.execute(() -> {
test2(1);
});
executorService.execute(() -> {
test2(2);
});
}
}
- Lock:依賴特殊的CPU指令许起,代碼實(shí)現(xiàn)十偶,ReentrantLock
對比:
- synchronized:不可中斷,適合競爭不激烈园细,可讀性號
- Lock:可中斷扯键,多樣化同步,競爭激烈是能維持常態(tài)
- Atomic:競爭激烈時能維持常態(tài)珊肃,比lock性能好;只能同步一個值馅笙。
可見性(java提供了synchronized和volatile 兩種方法來確甭浊牵可見性)
導(dǎo)致線程共享變量在線程間不可見的原因:
- 線程交叉執(zhí)行
- 重排序結(jié)合線程交叉執(zhí)行
- 共享變量更新后的值沒有在工作內(nèi)存與主內(nèi)存間及時更新
JMM關(guān)于synchronized的兩條規(guī)定:
- 線程解鎖前,必須把共享變量的最小值刷新到主內(nèi)存中
- 線程加鎖是董习,將清空工作內(nèi)存中共享變量的值烈和,從而使共享變量時需要從主內(nèi)存中重新讀取最新的值(注意,加鎖與解鎖是同一把鎖)
volatile
通過加入內(nèi)存屏障和禁止重排序優(yōu)化來實(shí)現(xiàn)可見性皿淋。
- 對于volatile變量寫操作時招刹,會在寫操作后加入一條store屏障指令,將本地內(nèi)存中的共享變量值刷新到主內(nèi)存窝趣。
-
對volatile變量度操作時疯暑,會在讀操作前加入一條load屏障指令,從主內(nèi)存中讀取共享變量哑舒。
volatile寫操作時
volatile讀操作時
volatile使用條件:
1妇拯、對變量寫操作不依賴于當(dāng)前值
2、該變量沒有包含在具有其他變量的不必要的式子中
綜上洗鸵,volatile特別適合用來做線程標(biāo)記量越锈,如下圖
有序性
java內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序膘滨,但是甘凭,重排序過程劊影響到單線程的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性火邓。
- volatile丹弱、synchronized、lock
happens-before原則
- 程序次序規(guī)則:一個線程內(nèi)贡翘,按照代碼順序蹈矮,書寫在前面的操作先行發(fā)生于書寫在后面的操作。
- 鎖定規(guī)則: 一個unLock操作先行發(fā)生于后面對同一個鎖的lock操作鸣驱。
- volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對于這個變量的讀操作泛鸟。
- 傳遞規(guī)則: 對于操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C踊东,則可以得出操作A先行發(fā)生于操作C北滥。
- 線程啟動規(guī)則: Thread對于start()方法先行發(fā)生于此線程的每個動作刚操。
- 線程中斷規(guī)則: 對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。
- 線程終結(jié)規(guī)則: 線程中所有的操作都先行發(fā)生于線程的終止檢測再芋,我們可以通過Tread.join()方法結(jié)束菊霜、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
- 對象終結(jié)原則: 一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始.