摘要:前言今天講的多線程的同步控制直接進(jìn)入正題重入鎖重入鎖可以完全代替,它需要類(lèi)來(lái)實(shí)現(xiàn)下面用一個(gè)簡(jiǎn)單的例子來(lái)實(shí)現(xiàn)重入鎖以上代碼打印出來(lái)的是,可以說(shuō)明也實(shí)現(xiàn)了線程同步它相比更加靈活,因?yàn)橹厝腈i實(shí)現(xiàn)了用戶自己加鎖,自己釋放鎖記得一定要釋放,不然其他線
前言
今天講的多線程的同步控制
直接進(jìn)入正題
重入鎖可以完全代替synchronized,它需要java.util.concurrent.locks.ReentrantLock類(lèi)來(lái)實(shí)現(xiàn)
下面用一個(gè)簡(jiǎn)單的例子來(lái)實(shí)現(xiàn)重入鎖:
public class ReentrantLockThread implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; @Override public void run() { for (int j=0;j<10000;j++){ lock.lock(); try { i++; }finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { ReentrantLockThread thread = new ReentrantLockThread(); Thread t1 = new Thread(thread); Thread t2 = new Thread(thread); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
以上代碼打印出來(lái)的i是20000,可以說(shuō)明ReentrantLock也實(shí)現(xiàn)了線程同步
它相比synchronized更加靈活,因?yàn)橹厝腈i實(shí)現(xiàn)了用戶自己加鎖.lock(),自己釋放鎖.unlock()(記得一定要釋放,不然其他線程無(wú)法進(jìn)入)
當(dāng)然重入鎖同一個(gè)對(duì)象可以加兩個(gè)鎖,但也要記得釋放兩個(gè)鎖(多釋放了會(huì)拋出異常,少釋放了那其它線程就進(jìn)不來(lái))
重入鎖的中斷功能也是它的高級(jí)功能之一:
在run()代碼塊中寫(xiě)入lock.lockInterruptibly()方法,當(dāng)線程實(shí)例使用t1.interrupt()時(shí),外部通知便會(huì)就會(huì)中斷t1線程
下面來(lái)一個(gè)簡(jiǎn)單示例代碼Demo:
public class interruptTest { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(){ public void run(){ while (true){ System.out.println("go"); if (Thread.currentThread().isInterrupted()){ break; } } } }; t1.start(); Thread.sleep(10001); t1.interrupt(); } }
發(fā)現(xiàn)t1線程實(shí)現(xiàn)interrupt()方法時(shí),線程實(shí)現(xiàn)代碼中的.isInterrupted()執(zhí)行了,并且中斷了t1線程
中斷功能可以很好的防止兩個(gè)線程間互相等待,出現(xiàn)死鎖的現(xiàn)象。
除了.interrupt()通知,要避免死鎖的另一種方法,就是限時(shí)等待:lock.tryLock()
我們來(lái)看下代碼:
public class MyThread implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; @Override public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)){ Thread.sleep(6000); }else { System.out.println("結(jié)束線程"); } } catch (InterruptedException e) { e.printStackTrace(); }finally { if (lock.isHeldByCurrentThread()){ lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { MyThread thread = new MyThread(); Thread t1 = new Thread(thread); Thread t2 = new Thread(thread); t1.start(); t2.start(); } }
lock.tryLock(5, TimeUnit.SECONDS)解釋下:
如果超過(guò)5秒還沒(méi)有得到鎖,就返回false,執(zhí)行else語(yǔ)句
如果成功獲得鎖,就返回true,執(zhí)行sleep語(yǔ)句
所以5秒后打印結(jié)束線程,結(jié)束的就是等待5秒后沒(méi)有拿到鎖的線程
當(dāng)然也可以不帶等待時(shí)間,直接if(lock.tryLock())
下面是對(duì)ReentrantLock的整理
lock():獲得鎖,如果鎖被占用,則等待 lockInterruptibly():獲得鎖,但優(yōu)先響應(yīng)中斷 tryLock():獲得鎖,如果成功返回true,如果失敗返回false unlock():釋放鎖Condition條件
Condition是和ReentrantLock關(guān)聯(lián)的
利用綁定的Condition我們可以讓線程在合適的時(shí)間等待,或者在某一特定時(shí)刻獲得通知繼續(xù)執(zhí)行
下面演示一段簡(jiǎn)單的Condition代碼
public class MyThread implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); @Override public void run() { try { lock.lock(); Thread.sleep(1000); System.out.println("t1拿到鎖,接著進(jìn)入等待,并且釋放鎖"); condition.await(); Thread.sleep(4000); System.out.println("t1又拿到鎖"); } catch (InterruptedException e) { }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { MyThread thread = new MyThread(); Thread t1 = new Thread(thread); t1.start(); Thread.sleep(5000); lock.lock(); System.out.println("主線程占用鎖"); condition.signal(); System.out.println("喚醒成功,主線程釋放鎖"); lock.unlock(); } }
await():會(huì)讓當(dāng)前線程進(jìn)入等待并且釋放鎖
singal();會(huì)喚醒一個(gè)在等待中的線程,當(dāng)然執(zhí)行方法的線程必須釋放鎖,被喚醒的線程才能得到鎖
怎樣才能規(guī)定進(jìn)入一段代碼的線程數(shù)
這里我們使用信號(hào)量,在外面定義Semaphore semp = new Semaphore(10);,這樣簡(jiǎn)單的設(shè)定了5個(gè)線程
在run()方法中semp.acquire();表示獲得了10個(gè)中的其中一個(gè)許可證
當(dāng)你的工作代碼完成時(shí),依舊在run()方法的后面寫(xiě)上semp.release();,許可證被釋放(跟鎖一個(gè)道理)
我們知道讀不會(huì)響應(yīng)數(shù)據(jù),寫(xiě)會(huì)影響數(shù)據(jù)
所以我們真正操作的時(shí)候要求只讀的那些線程可以一起執(zhí)行,不用同步操作
而與寫(xiě)有關(guān)的線程全部要同步,以保護(hù)數(shù)據(jù)的安全
那么我們?cè)鯓硬拍茏龅街蛔x線程不用同步呢
這里需要用到讀寫(xiě)鎖,下面演示一段讀寫(xiě)鎖的簡(jiǎn)單例子:
public class ReadWriteThread { 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 int handleRead(Lock lock) throws InterruptedException{ try { lock.lock(); Thread.sleep(5000); //模擬讀線程 System.out.println("讀完成"); return value; } finally { lock.unlock(); } } //寫(xiě) public void handleWrite(Lock lock,int index) throws InterruptedException { try { lock.lock(); Thread.sleep(5000); value=index; System.out.println("寫(xiě)完成"); }finally { lock.unlock(); } } public static void main(String[] args) { ReadWriteThread demo = new ReadWriteThread(); Thread t1 = new Thread(){ public void run(){ try { demo.handleRead(readLock); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t2 = new Thread(){ public void run(){ try { demo.handleRead(readLock); demo.handleWrite(writeLock,2); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); t2.start(); } }
執(zhí)行結(jié)果可以發(fā)現(xiàn),對(duì)于只讀的方法,我們給予readLock鎖,而寫(xiě)的方法給予writeLock鎖
如此這般,只讀的方法可以并行,而讀寫(xiě)只能串行
倒計(jì)時(shí)器用來(lái)控制線程等待,它可以讓一個(gè)線程等待直到倒計(jì)時(shí)結(jié)束,再開(kāi)始執(zhí)行
還是通過(guò)實(shí)例來(lái)讓大家知道什么是倒計(jì)時(shí)器,并且它能有什么作用
這個(gè)例子的需求是:要在主線程之前完成之個(gè)類(lèi)線程才能繼續(xù)主線程:
public class CountDownLatchThread implements Runnable{ private static CountDownLatch countDownLatch = new CountDownLatch(10); private static CountDownLatchThread countDownLatchThread = new CountDownLatchThread(); @Override public void run() { try { Thread.sleep(1000); System.out.println("此線程工作完成"); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(10); //創(chuàng)建一個(gè)固定大小為10的線程池 for (int i=0;i<10;i++){ executorService.submit(countDownLatchThread); } countDownLatch.await(); System.out.println("10個(gè)任務(wù)完成"); executorService.shutdown(); } }
countDownLatch.await();:讓主線程進(jìn)入等待,等待所有10個(gè)線程完成
countDownLatch.countDown();:說(shuō)明這個(gè)線程已完成,并進(jìn)入統(tǒng)計(jì)
線程池和數(shù)據(jù)庫(kù)連接池一樣,事先準(zhǔn)備好線程,當(dāng)程序需要線程時(shí),調(diào)用線程池中空閑的線程,當(dāng)工作線程工作完畢時(shí),重新放回線程池
JDK提供了一套Executor框架,本質(zhì)是線程池。
框架中的成員變量在java.util.concurrent包中,是JDK并發(fā)包的核心類(lèi)
其中ThreadPoolExecutor實(shí)現(xiàn)了Executor接口,任何Runnable都可以被ThreadPoolExecutor線程池調(diào)度
Executor提供了各種類(lèi)型的線程池,主要有以下工廠方法創(chuàng)建不同的線程池:
newFixedThreadPool()方法:返回一個(gè)固定線程數(shù)量的線程池,當(dāng)一個(gè)新任務(wù)提交時(shí),如果有空閑線程,立即執(zhí)行,如果沒(méi)有空閑線程,新任務(wù)會(huì)被放在一個(gè)等待隊(duì)列中去等待空閑線程
newCachedThreadPool()方法:返回一個(gè)根據(jù)實(shí)際情況調(diào)整線程數(shù)量的線程池,
newSingleThreadScheduledExecutor()方法:返回一個(gè)對(duì)象,可放線程數(shù)量為1,但是這個(gè)線程池拓展了計(jì)劃任務(wù)功能,可以規(guī)定執(zhí)行時(shí)間、周期性等等
上面這些線程池的源碼其實(shí)都是用ThreadPoolExecutor實(shí)現(xiàn):
我們來(lái)看下ThreadPoolExecutor的構(gòu)造函數(shù):
public ThreadPoolExecutor(int corePoolSize, //指定線程池中線程的數(shù)量 int maximumPoolSize,//指定線程池中最大線程數(shù)量 long keepAliveTime, //當(dāng)線程池中線程數(shù)量超過(guò)corePoolSize時(shí),多余線程的存活時(shí)間 TimeUnit unit, //keepAliveTime的單位 BlockingQueueworkQueue, //等待任務(wù)隊(duì)列,被提交都是尚未執(zhí)行的任務(wù) ThreadFactory threadFactory, //線程工廠,用于創(chuàng)建線程,一般默認(rèn) RejectedExecutionHandler handler //拒絕策略,當(dāng)任務(wù)太多時(shí),如何拒絕任務(wù) }
下面我來(lái)講講BlockingQueue:
在ThreadPoolExecutor構(gòu)造器中,有以下幾種BlockingQueue:
1.直接提交隊(duì)列:有SynchronousQueue對(duì)象提供,提交的任務(wù)如果沒(méi)有空閑線程嘗試新建線程,如果線程數(shù)量已達(dá)到最大,則執(zhí)行拒絕策略
2.有界的任務(wù)隊(duì)列:使用ArrayBlockingQueue對(duì)象實(shí)現(xiàn),構(gòu)造器帶一個(gè)任務(wù)的容量參數(shù),若等待隊(duì)列已滿,總線程不大于maximumPoolSize的前提下,創(chuàng)建新的線程執(zhí)行任務(wù),若大于,則執(zhí)行拒絕策略
3.無(wú)界的任務(wù)隊(duì)列:使用LinkedBlockingQueue來(lái)實(shí)現(xiàn),和有界相比,除非系統(tǒng)資源耗盡,否則無(wú)界的任務(wù)隊(duì)列不存在任務(wù)入隊(duì)失敗的情況
4.有限任務(wù)隊(duì)列:通過(guò)PriorityBlockingQueue實(shí)現(xiàn),可以控制任務(wù)的執(zhí)行先后順序
再來(lái)看newFixedThreadPool(),它使用了無(wú)界任務(wù)隊(duì)列,并且corePoolSize和maximumPoolSize一樣大,因?yàn)閷?duì)于固定大小的線程池,不存在線程數(shù)量的動(dòng)態(tài)變化,當(dāng)任務(wù)提交非常頻繁時(shí),可能會(huì)耗盡系統(tǒng)資源
而newCachedThreadPool()方法返回corePoolSize為0,maximumPoolSize無(wú)限大的線程池,使用了SynchronousQueue隊(duì)列,當(dāng)任務(wù)執(zhí)行完畢后,由于corePoolSize為0,空閑線程會(huì)在指定時(shí)間(60s)回收
講完了BlockingQueue我們來(lái)講下RejectedExecutionHandler拒絕策略
JDK內(nèi)置了四種拒絕策略:
1.AbortPolicy:直接拋出異常,阻止系統(tǒng)正常工作
2.CallerRunsPolicy:只要線程池沒(méi)有關(guān)閉,該策略直接調(diào)用工作線程運(yùn)行當(dāng)前被丟棄的任務(wù)
3.DiscardOledestPolicy:丟棄最老的一個(gè)等待任務(wù),也就是即將被執(zhí)行的一個(gè)任務(wù),并嘗試再次提交當(dāng)前任務(wù)
4.DiscardPolicy:默默地丟棄無(wú)法處理的任務(wù),如果運(yùn)行任務(wù)丟失,這是最好的一個(gè)策略
當(dāng)然我們也可以自己寫(xiě)拒絕策略
下面我來(lái)寫(xiě)一個(gè)打印出被拒絕的策略(而不是選擇拋異常,因?yàn)閽伄惓N覀冞€要去捕捉異常,如果沒(méi)有捕捉到會(huì)導(dǎo)致系統(tǒng)奔潰)
public class ThreadPoolTest { public static class TestThread implements Runnable{ @Override public void run() { System.out.println(System.currentTimeMillis()+"線程ID:"+Thread.currentThread().getId()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { TestThread testThread = new TestThread(); ExecutorService es = new ThreadPoolExecutor(5,5,0L, TimeUnit.SECONDS, new ArrayBlockingQueue(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler(){ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("等待線程被拒絕"); } }); for (int i=0;i 來(lái)看下運(yùn)行結(jié)果:
1511833277724線程ID:13 1511833277771線程ID:10 1511833277771線程ID:14 1511833277771線程ID:12 等待線程被拒絕 等待線程被拒絕 1511833277833線程ID:13 1511833277833線程ID:11可以發(fā)現(xiàn),我們自定義的線程池和拒絕策略完美的執(zhí)行了
并發(fā)高效容器下面我介紹給大家?guī)讉€(gè)非常好用的工具類(lèi)(當(dāng)然都是線程安全的)
1.ConcurrentHashMap:這是一個(gè)高效的hashMap
2.CopyOnwriteArrayList:在讀多寫(xiě)少的場(chǎng)合這個(gè)list非常好用,遠(yuǎn)勝與Vector
3.ConCurrentLinkedQueue:高效的并發(fā)隊(duì)列,使用鏈表實(shí)現(xiàn),可以看做是一個(gè)線程安全的LinkedList
4.BlockingQueue:這個(gè)接口上面說(shuō)過(guò),表示阻塞隊(duì)列,非常適合用于作為數(shù)據(jù)共享的通道
5.ConcurrentSkipListMap:這是一個(gè)Map,使用跳表的數(shù)據(jù)結(jié)構(gòu)進(jìn)行快速的查找如果并不追求高效,我們可以使用Collections類(lèi)幫助把線程不安全的容器轉(zhuǎn)換為線程安全
如將HashMap轉(zhuǎn)換為線程安全:Map map = Collections.synchronizedMap(new HashMap()); 當(dāng)然可以使用CAS操作來(lái)替代synchronized
今天就先到這里,大家可以看看這些內(nèi)容的拓展
記得點(diǎn)關(guān)注看更新,謝謝閱讀
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70665.html
摘要:今天就先到這里,大家可以看看這些內(nèi)容的拓展記得點(diǎn)關(guān)注看更新,謝謝閱讀 前言 這是一個(gè)長(zhǎng)篇博客,希望大家關(guān)注我并且一起學(xué)習(xí)java高并發(fā)廢話不多說(shuō),直接開(kāi)始 并行和并發(fā) 并行:多個(gè)線程同時(shí)處理多個(gè)任務(wù)并發(fā):多個(gè)線程處理同個(gè)任務(wù),不一定要同時(shí) 下面用圖來(lái)描述并行和并發(fā)的區(qū)別:(實(shí)現(xiàn)和虛線表示兩個(gè)不同的線程) showImg(https://segmentfault.com/img/bVYT...
摘要:可以用代替可以用代替定義的對(duì)象的值是不可變的今天就先到這里,大家可以看看這些內(nèi)容的拓展記得點(diǎn)關(guān)注看更新,謝謝閱讀 前言 java高并發(fā)第二篇講的是java線程的基礎(chǔ)依舊不多說(shuō)廢話 線程和進(jìn)程 進(jìn)程是操作系統(tǒng)運(yùn)行的基礎(chǔ),是一個(gè)程序運(yùn)行的實(shí)體,windows上打開(kāi)任務(wù)管理器就能看到進(jìn)程線程是輕量級(jí)的進(jìn)程,是程序執(zhí)行的最小單位,是在進(jìn)程這個(gè)容器下進(jìn)行的 線程基本操作 新建一個(gè)線程類(lèi)有兩種方式...
摘要:前言這篇主要來(lái)講解多線程中一個(gè)非常經(jīng)典的設(shè)計(jì)模式包括它的基礎(chǔ)到拓展希望大家能夠有所收獲生產(chǎn)者消費(fèi)者模式簡(jiǎn)述此設(shè)計(jì)模式中主要分兩類(lèi)線程生產(chǎn)者線程和消費(fèi)者線程生產(chǎn)者提供數(shù)據(jù)和任務(wù)消費(fèi)者處理數(shù)據(jù)和任務(wù)該模式的核心就是數(shù)據(jù)和任務(wù)的交互點(diǎn)共享內(nèi)存緩 前言 這篇主要來(lái)講解多線程中一個(gè)非常經(jīng)典的設(shè)計(jì)模式包括它的基礎(chǔ)到拓展希望大家能夠有所收獲 生產(chǎn)者-消費(fèi)者模式簡(jiǎn)述 此設(shè)計(jì)模式中主要分兩類(lèi)線程:生產(chǎn)者...
摘要:前言本篇主要講解如何去優(yōu)化鎖機(jī)制或者克服多線程因?yàn)殒i可導(dǎo)致性能下降的問(wèn)題線程變量有這樣一個(gè)場(chǎng)景,前面是一大桶水,個(gè)人去喝水,為了保證線程安全,我們要在杯子上加鎖導(dǎo)致大家輪著排隊(duì)喝水,因?yàn)榧恿随i的杯子是同步的,只能有一個(gè)人拿著這個(gè)唯一的杯子喝 前言 本篇主要講解如何去優(yōu)化鎖機(jī)制或者克服多線程因?yàn)殒i可導(dǎo)致性能下降的問(wèn)題 ThreadLocal線程變量 有這樣一個(gè)場(chǎng)景,前面是一大桶水,10個(gè)...
摘要:只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙只有動(dòng)手,你才能真正掌握一門(mén)技術(shù)持續(xù)更新中項(xiàng)目地址求求求源碼系列跟一起學(xué)如何寫(xiě)函數(shù)庫(kù)中高級(jí)前端面試手寫(xiě)代碼無(wú)敵秘籍如何用不到行代碼寫(xiě)一款屬于自己的類(lèi)庫(kù)原理講解實(shí)現(xiàn)一個(gè)對(duì)象遵循規(guī)范實(shí)戰(zhàn)手摸手,帶你用擼 Do it yourself!!! 只有動(dòng)手,你才能真的理解作者的構(gòu)思的巧妙 只有動(dòng)手,你才能真正掌握一門(mén)技術(shù) 持續(xù)更新中…… 項(xiàng)目地址 https...
閱讀 1363·2021-09-28 09:43
閱讀 4163·2021-09-04 16:41
閱讀 1928·2019-08-30 15:44
閱讀 3750·2019-08-30 15:43
閱讀 787·2019-08-30 14:21
閱讀 2044·2019-08-30 11:00
閱讀 3329·2019-08-29 16:20
閱讀 1932·2019-08-29 14:21