注:其一、本文章為作者讀完《實戰(zhàn)Java高并發(fā)程序設計》之后所總結的知識屹耐,其中涵蓋了每一章節(jié)的精髓之處椿猎。其二惶岭、文章中一部分代碼直接引自書中。
一犯眠、并行基礎
1.1線程的基本操作
1.1.1.終止線程
- Thread.stop(): 在結束線程時會直接終止線程
在不清楚的情況下不可隨便使用按灶,因為強行終止可能會損壞文件中的數據
1.1.2.線程中斷
- static native void sleep(long millis) throws InterruptedException (暫先了解):
Thraed.sleep()方法
會讓當前線程休眠若干時間,它會拋出一個InterruptedException
中斷異常筐咧。但是InterruptedException
不是運行時異常鸯旁,也就是說程序必須主動捕獲并處理它。線程在sleep()
休眠時量蕊,如果被中斷铺罢,這個異常就會產生。
- void Thread.interrupt(): 一個實例方法,通知目標線程中斷残炮,會設置一個中斷標志
可與中斷判斷方法搭配來實現中斷線程的目的(例如:如果判斷方法返回數據為中斷狀態(tài)就退出循環(huán)終止線程)
- boolean Thraed.isInterrupted(): 用于判斷當前線程是否被中斷
- static boolean Thread.interrupted(): 判斷當前線程是否被中斷韭赘,并清除當前中斷狀態(tài)
1.1.3.等待(wait)和通知(notify)
- final void wait() throws InterruptedException //
- final native void notify()
- final native void notifyAll()
這兩個方法不屬于
Thraed類
,而是屬于Object類
势就。這兩個方法在執(zhí)行之前都需要先獲得相應對象的鎖泉瞻,wait()方法
執(zhí)行之后會釋放這個鎖脉漏,但是notify()
不會立刻立刻釋放sycronized(object)
中的object鎖
,必須要等notify()
所在線程執(zhí)行完synchronized(object)
塊中的所有代碼才會釋放這把鎖袖牙。當一個線程調用了object.wait()
之后侧巨,它會進入該對象的等待隊列,該隊列中可能會有多個等待同一對象的線程鞭达。當對象調用object.notify()
的時候刃泡,它會從等待隊列中隨機喚醒一個線程(這是不公平的)。線程被釋放之后不會立即執(zhí)行碉怔,而是嘗試獲取對象的鎖烘贴,如果獲取失敗,還需要先等待對象的鎖撮胧。notifyAll()
會喚醒隊列中所有的線程
1.1.4.掛起(suspend)和繼續(xù)執(zhí)行(resume)線程
在JDK文檔中這兩個方法已經被標注為廢棄方法
- suspend(): 方法在導致線程暫停的同時不會釋放任何鎖資源桨踪,直到對應的線程執(zhí)行了resume()方法才能使被掛起的線程繼續(xù)執(zhí)行
suspend()
所占用的鎖不會被釋放,因此可能導致整個系統(tǒng)工作不正常芹啥。有時resume()方法寫在suspend(0方法之后锻离,但是由于時間先后順序的緣故,resume()
就可能無法生效墓怀,這會導致線程被永遠掛起汽纠,并且一直占用對象的鎖,這對于系統(tǒng)來說是非常致命的
1.1.5.等待線程結束(join)和謙讓(yield)
- final void join() throws InterruptedException
- final synchronized void join(long millis) throws InterruptedException
第一個join()方法
會無限等待傀履,它會一直阻塞當前線程虱朵,直到目標線程運行結束。第二個join()方法
給出了等待時間钓账,如果超過了等待時間線程還在繼續(xù)執(zhí)行碴犬,當前線程就會繼續(xù)執(zhí)行。
- static native void yield()
yield()方法執(zhí)行之后會是當前線程釋放CPU梆暮,釋放CPU之后會重新爭奪CPU資源的爭奪服协,但不一定能分配到資源
1.2.volatile
為變量添加關鍵字volatile聲明之后,保證了該變量的可見性啦粹,應用程序范圍內的所有線程都能“看到”這個改動偿荷。但是,volatile不能保證一些復合操作的原子性唠椭,例如:
static volatile int i=0;
public static class VolatileDemo Runnable{
@Override
public void run() {
for(int k=0;k<10000;k++)
i++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads=new Thread[10];
for(int i=0;i<10;i++){
threads[i]=new Thread(new VolatileDemo());
threads[i].start();
}
for(int i=0;i<10;i++){
threads[i].join();
}
System.out.println(i);
}
按我們的猜測跳纳,這段代碼執(zhí)行之后應該得到的結果應該為100000,但是得到的結果總是小于100000泪蔫,課件volatile并不能保證復合操作的原子性
1.3.線程組
- ThreadGroup tg = new ThreadGroup("PrintGroup");
線程組的優(yōu)點就是更方便管理一對數組.在線程創(chuàng)建的時候為線程添加第一個參數為線程組的名稱棒旗,第三個參數為現成的名稱即可
例如:Thraed t1 = new Thraed(tg,new RunnableDemo(),"T1");
線程組中有一個值得注意的方法:stop(),它會停止線程組中所有的線程,但使用時同樣需要謹慎
1.4.守護線程(Daemon)
- thread.setDaemo(true);
設置守護線程必須在線程start()之前設置铣揉,否則會拋出異常:
java.lang.IllegalThreadStateException
饶深。守護線程會隨著main函數結束而結束,會在main函數休眠2秒后退出
1.5.關鍵字synchronized
synchronized的三種用法:
- 指定加鎖對象:給給定對象加鎖逛拱,進入同步代碼塊前要獲得給定對象的鎖
- 直接作用于實例方法:相當于對當前實例進行加鎖敌厘,進入同步代碼塊前要獲得當前實例的鎖
- 直接作用于靜態(tài)方法:相當于對當前類進行加鎖,進入同步代碼塊前要獲得當前類的鎖
作用于指定對象與作用于實例方法都需要注意:加鎖的對象一定要是同一個對象朽合,否則代碼塊就有可能無法正確執(zhí)行俱两。如果是作用于靜態(tài)方法,那么即使兩個線程指向的不是同一個對象曹步,也能保證程序正確執(zhí)行
以下兩個代碼塊所產生的效果是相同的(代碼引用自《實戰(zhàn)Java高并發(fā)程序設計》)
public class AccountingSync2 implements Runnable{
static AccountingSync2 instance=new AccountingSync2();
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
***Thread t1=new Thread(instance);
Thread t2=new Thread(instance);***
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
public class AccountingSyncBad implements Runnable{
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<10000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
***Thread t1=new Thread(new AccountingSyncBad());
Thread t2=new Thread(new AccountingSyncBad());***
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
二宪彩、JDK并發(fā)包
2.1.同步控制
2.1.1.synchronized的功能擴展-重入鎖
重入鎖使用java.util.concurrent.locks.ReentrantLock
類來實現,簡單使用案例(部分):
public class ReenterLock implements Runnable{
public static ReentrantLock lock=new ReentrantLock();
public static int i=0;
@Override
public void run() {
for(int j=0;j<10000000;j++){
lock.lock();
try{
i++;
}finally{
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
***ReenterLock tl=new ReenterLock();
Thread t1=new Thread(tl);
Thread t2=new Thread(tl);***
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
重入鎖讲婚,顧名思義尿孔,此鎖可反復進入,但是一個線程獲得幾次鎖筹麸,就要釋放鎖時也要釋放相同的次數活合,釋放次數多了會拋出異常:
java.lang.IllegalMonitorStateException
lock.lock();
lock.lock();
try{
i++;
}finally{
lock.unlock();
lock.unlock();
}
2.1.1.1.中斷響應
中斷響應處理死鎖問題:如果程序中所有獲取鎖的方式均使用可以對中斷作出響應的方法獲取鎖,那么就可以在主線程main睡眠的時候中斷一個線程物赶,該線程就會放棄對鎖的申請
2.1.1.2.鎖申請等待限時
鎖申請等待限時 為避免死鎖的另一種處理方式(重入鎖ReentrantLock實現)
public class TimeLock implements Runnable{
public static ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(6000);
}else{
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{lock.unlock();}
}
public static void main(String[] args) {
TimeLock tl=new TimeLock();
Thread t1=new Thread(tl);
Thread t2=new Thread(tl);
t1.start();
t2.start();
}
}
在這里白指,tryLock()方法有兩個參數,第一個參數為等待時長酵紫,第二個參數為計時單位告嘲,這里兩個參數表示的意思是5秒內嘗試獲得鎖,如果超過5秒還未獲取到鎖憨闰,就返回false状蜗,獲取成功返回true
ReentrantLock.tryLock()方法也可以不設置參數:當前線程會嘗試獲得鎖需五,獲取成功返回true鹉动,如果鎖正在被其他線程占用,當前線程也不會等待宏邮,而是直接返回true
2.1.1.3.公平鎖
重入鎖允許我們對其公平性進行設置
public ReentrantLock(boolean fair)
當參數fair為true時泽示,表示鎖是公平的。公平鎖體現在:為線程按請求按順序分配鎖蜜氨。但是實現公鎖需要維護一個有序隊列械筛,因此公平鎖的實現成本比較高。如果沒有特殊要求則不需要使用公平鎖
2.1.1.4.ReentrantLock的幾個重要方法
- lock(): 獲得鎖飒炎,如果所已經被占用則等待
- lockInterruptibly(): 獲得鎖埋哟,但優(yōu)先響應中斷
- tryLock(): 嘗試獲得鎖,如果成功,則返回true赤赊,失敗返回false闯狱。該方法不等待,立即返回
- tryLock(long time,TimeUnit unit): 在給定時間內嘗試獲得鎖
- unlock(): 釋放鎖
2.1.2.Condition
Condition與重入鎖相關聯的
( wait()與notify()方法是與synchronized關鍵字搭配使用 )
創(chuàng)建: 通過locak接口(重入鎖實現了這一接口)的Condition newCondition()方法可以生成一個與當前重入鎖綁定的Condition實例
Condition接口提供的基本方法如下:
- void await() throws InterruptedException;
- void awaitUninterruptibly();
- void awaitNanos(long nanosTimeout) throws InterruptedException;
- boolean await(long time,TimeUnit unit) throws InterruptedException;
- boolean awaitUntil(Date deadline) throws InterruptedException;
- void signal();
- void signalAll();
使用
Condition.await()
方法時抛计,要求線程持有相關的重入鎖哄孤,在Condition.await()
方法調用之后,這個線程會釋放這把鎖吹截。signal()
方法執(zhí)行后瘦陈,系統(tǒng)會從當前Condition對象的等待隊列中喚醒一個線程。在signal()方法執(zhí)行后一般要釋放相關的鎖波俄,讓給被喚醒的線程
2.1.3.信號量(Semaphore)
信號量為多線程協作提供了更為強大的控制方法晨逝。廣義上說,信號量是對鎖的擴展
構造函數:
public Semaphore(int permits) //permits為準入數
public Semaphore(int permits,boolean fair) //第二個參數可以指定是否公平
信號量的主要邏輯方法:
public void acquire()
public void acquireUninterruptibly()
public boolean tryAcquire()
public boolean tryAcquire(long timeout,TimeUnit unit)
public void release()
acquire()
嘗試或得一個準入的許可懦铺,線程如果沒有獲得就等待咏花,直到有線程釋放一個許可或當前線程被中斷。
acquireUninterruptibly()
與acquire()
類似阀趴,但是不響應中斷
tryAcquire()
嘗試獲得一個許可昏翰,成功返回true,失敗返回false刘急,它不會進行等待
release()
用于在線程訪問資源結束后釋放一個許可棚菊。
簡單的例子:
public class SemapDemo implements Runnable {
final Semaphore semp = new Semaphore(5);
@Override
public void run() {
try {
semp.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId() + ":done!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semp.release();
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemapDemo demo = new SemapDemo();
for (int i = 0; i < 20; i++) {
exec.submit(demo);
}
}
}
2.1.4.ReadWriteLock讀寫鎖
讀寫分離鎖:讀寫分離所可以有效地幫助減少鎖競爭,提升系統(tǒng)性能叔汁。
通過重入讀寫鎖分別生成讀鎖和寫鎖统求,減少沒必要的競爭,提高運行效率
讀 | 寫 | |
---|---|---|
讀 | 非阻塞 | 阻塞 |
寫 | 阻塞 | 阻塞 |
在下面的例子中据块,讀線程完全并行码邻,而寫會阻塞讀
如果用注釋掉的代碼分別替換前一行代碼,所有的讀和寫線程之間都必須互相等待另假,效率將會大大降低
private static Lock lock=new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException{
try{
lock.lock(); //模擬讀操作
Thread.sleep(1000); //讀操作的耗時越多像屋,讀寫鎖的優(yōu)勢就越明顯
return value;
}finally{
lock.unlock();
}
}
public void handleWrite(Lock lock,int index) throws InterruptedException{
try{
lock.lock(); //模擬寫操作
Thread.sleep(1000);
value=index;
}finally{
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo demo=new ReadWriteLockDemo();
Runnable readRunnale=new Runnable() {
@Override
public void run() {
try {
// demo.handleRead(readLock);
demo.handleRead(lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnale=new Runnable() {
@Override
public void run() {
try {
// demo.handleWrite(writeLock,new Random().nextInt());
demo.handleWrite(lock,new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for(int i=0;i<18;i++){
new Thread(readRunnale).start();
}
for(int i=18;i<20;i++){
new Thread(writeRunnale).start();
}
}
2.1.5.倒計時器:CountDownLatch
當使用計數器計時的時候,每次執(zhí)行完一個線程就執(zhí)行CountDownLatch.countDown()使計數count-1边篮,直到count為0的時候解除CountDownLatch.await()對主線程main的阻塞
構造函數:
public CountDownLatch(int count)
示例代碼:
/**
* 倒數計時器
* @author Geym
*
*/
public class CountDownLatchDemo implements Runnable {
static final CountDownLatch end = new CountDownLatch(10);
static final CountDownLatchDemo demo=new CountDownLatchDemo();
@Override
public void run() {
try {
//模擬檢查任務
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println("check complete");
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(10);
for(int i=0;i<10;i++){
exec.submit(demo);
}
//等待檢查
end.await();
//發(fā)射火箭
System.out.println("Fire!");
exec.shutdown();
}
}
2.1.6.循環(huán)柵欄:CyclicBarrier
CyclicBarrier是一種多線程并發(fā)控制工具
構造函數:
public CyclicBarrier(int parties,Runnable barrierAction)
* int parties: 表示計數總數
* Runnable barrierAction: 當計數達到parties之后系統(tǒng)所要執(zhí)行的動作己莺。
* barrierAction在線程集合完畢時執(zhí)行一次,所有線程執(zhí)行完畢會再次執(zhí)行一次
大概意思是柵欄有一個計時器戈轿,當有一個線程請求執(zhí)行時凌受,柵欄計數器+1,當到達指定n后思杯,循環(huán)柵欄一次執(zhí)行這n個線程胜蛉。然后如此循環(huán)進行
案例: 司令下達命令,要求10個士兵一起去完成一項任務。這時就會要求10個士兵先集合報道誊册,然后一起去執(zhí)行任務奈梳。當10個士兵全部都完成任務后,司令才能宣布任務完成
代碼實現:
public class CyclicBarrierDemo {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic, String soldierName) {
this.cyclic = cyclic;
this.soldier = soldierName;
}
public void run() {
try {
//等待所有士兵到齊
cyclic.await();
doWork();
//等待所有士兵完成工作
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt()%10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + ":任務完成");
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
public BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
public void run() {
if (flag) {
System.out.println("司令:[士兵" + N + "個解虱,任務完成攘须!]");
} else {
System.out.println("司令:[士兵" + N + "個,集合完畢殴泰!]");
flag = true;
}
}
}
public static void main(String args[]) throws InterruptedException {
final int N = 10;
Thread[] allSoldier=new Thread[N];
boolean flag = false;
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
//設置屏障點于宙,主要是為了執(zhí)行這個方法
System.out.println("集合隊伍!");
for (int i = 0; i < N; ++i) {
System.out.println("士兵 "+i+" 報道悍汛!");
allSoldier[i]=new Thread(new Soldier(cyclic, "士兵 " + i));
allSoldier[i].start();
}
}
}
輸出結果:
集合隊伍捞魁!
士兵 0 報道!
//篇幅有限离咐,省略其他幾個士兵
士兵 9 報道谱俭!
司令:[士兵10個,集合完畢宵蛀!]
士兵0:任務完成
//篇幅有限昆著,省略其他幾個士兵
士兵9:任務完成
司令:[士兵10個,任務完成]
CyclicBarrier.await()
方法可能會拋出兩個異常术陶,第一個是中斷異常凑懂,這里不多做描述。第二個異常是BrokenBarrierException
梧宫,遇到這個異常接谨,則表示當前的CyclicBarrier已經破損
2.1.7.線程阻塞工具類:LockSupport
LockSupport
可以在線程內任意位置讓線程阻塞。與Thread.suspend()
方法相比塘匣,它彌補了由于resume()
方法發(fā)生導致的線程無法繼續(xù)執(zhí)行的情況脓豪。與Object.wait()
相比,他不需要先獲得某個對象的鎖忌卤。也不會拋出InterruptedException
異常
·
LockSupport的靜態(tài)方法park()可以阻塞當前線程扫夜,類似的還有parkNanos()、parkUntil()等方法埠巨。LockSupport的靜態(tài)方法unpark()可以讓線程繼續(xù)執(zhí)行历谍。代碼書寫正確了,即使無法保證unpark()會發(fā)生在park()之前辣垒,程序也會正確的執(zhí)行。即使unpark()發(fā)生在park()之前印蔬,它也可以使下一次的park()方法操作立即返回勋桶。因為LockSupport類使用類似信號量的機制。
·
LockSupport.park()方法還能支持中斷影響,但它不會拋出InterruptedException異常例驹,它只是默默返回捐韩,但是可以從Thread.interrupted()等方法中獲得中斷標記
3.1.8.Guava和RateLimiter
Guava是Google的一個核心庫,RateLimiter是Guava的一款限流工具
·
漏桶算法:利用一個緩沖區(qū)鹃锈,當有請求進入系統(tǒng)時荤胁,無論請求的速率如何,都現在緩存區(qū)內保存屎债,然后以固定的流速流出緩存區(qū)進行處理仅政。**特點:無論外界請求壓力如何,漏桶算法總是以固定的流速處理數據盆驹。漏桶的容積和流出速率是該算法的兩個重要參數
·
令牌桶算法:一種反向的漏桶算法圆丹。在令牌桶算法中,桶中存放的不再是請求躯喇,而是令牌辫封。處理程序只有拿到令牌后,才能對請求進行處理廉丽。如果沒有令牌倦微,那么處理程序要么丟棄請求,要么等待可用的令牌正压。為了限制流速璃诀,該算法在每個單位時間內產生一定量的令牌放入桶中
RateLimiter正是采用了令牌桶算法。下列是RateLimiter的使用方法:
public class RateLimiterDemo {
static RateLimiter limiter = RateLimiter.create(2);
public static class Task implements Runnable {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String args[]) throws InterruptedException {
for (int i = 0; i < 50; i++) {
limiter.acquire();
new Thread(new Task()).start();
}
}
}
輸出如下:
1527947609270
1527947609768
1527947600268
1527947600768 //ms時間 每秒輸出兩個結果
但是在一些系統(tǒng)無法處理請求的場景中蔑匣,為了保證服務質量劣欢,更傾向于直接丟棄過載請求(
tryAcquire()
)
3.2.線程池
線程池將在后續(xù)更新中總結...
文集推薦:
Java基礎方法集1
Python基礎知識完整版
Spring Boot學習筆記
Linux指令進階
Java高并發(fā)編程
SpringMVC基礎知識進階
Mysql基礎知識完整版
健康管理系統(tǒng)學習花絮(學習記錄)
Node.js基礎知識(隨手筆記)
MongoDB基礎知識
Dubbo學習筆記
Vue學習筆記(隨手筆記)
聲明:發(fā)表此文是出于傳遞更多信息之目的。若有來源標注錯誤或侵犯了您的合法權益裁良,請作者持權屬證明與本我們(QQ:981086665凿将;郵箱:981086665@qq.com)聯系聯系,我們將及時更正价脾、刪除牧抵,謝謝。