摘要:前言今天講的多線程的同步控制直接進入正題重入鎖重入鎖可以完全代替,它需要類來實現下面用一個簡單的例子來實現重入鎖以上代碼打印出來的是,可以說明也實現了線程同步它相比更加靈活,因為重入鎖實現了用戶自己加鎖,自己釋放鎖記得一定要釋放,不然其他線
前言
今天講的多線程的同步控制
直接進入正題
重入鎖可以完全代替synchronized,它需要java.util.concurrent.locks.ReentrantLock類來實現
下面用一個簡單的例子來實現重入鎖:
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); } }
以上代碼打印出來的i是20000,可以說明ReentrantLock也實現了線程同步
它相比synchronized更加靈活,因為重入鎖實現了用戶自己加鎖.lock(),自己釋放鎖.unlock()(記得一定要釋放,不然其他線程無法進入)
當然重入鎖同一個對象可以加兩個鎖,但也要記得釋放兩個鎖(多釋放了會拋出異常,少釋放了那其它線程就進不來)
重入鎖的中斷功能也是它的高級功能之一:
在run()代碼塊中寫入lock.lockInterruptibly()方法,當線程實例使用t1.interrupt()時,外部通知便會就會中斷t1線程
下面來一個簡單示例代碼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(); } }
發現t1線程實現interrupt()方法時,線程實現代碼中的.isInterrupted()執行了,并且中斷了t1線程
中斷功能可以很好的防止兩個線程間互相等待,出現死鎖的現象。
除了.interrupt()通知,要避免死鎖的另一種方法,就是限時等待:lock.tryLock()
我們來看下代碼:
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("結束線程"); } } 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)解釋下:
如果超過5秒還沒有得到鎖,就返回false,執行else語句
如果成功獲得鎖,就返回true,執行sleep語句
所以5秒后打印結束線程,結束的就是等待5秒后沒有拿到鎖的線程
當然也可以不帶等待時間,直接if(lock.tryLock())
下面是對ReentrantLock的整理
lock():獲得鎖,如果鎖被占用,則等待 lockInterruptibly():獲得鎖,但優先響應中斷 tryLock():獲得鎖,如果成功返回true,如果失敗返回false unlock():釋放鎖Condition條件
Condition是和ReentrantLock關聯的
利用綁定的Condition我們可以讓線程在合適的時間等待,或者在某一特定時刻獲得通知繼續執行
下面演示一段簡單的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拿到鎖,接著進入等待,并且釋放鎖"); 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():會讓當前線程進入等待并且釋放鎖
singal();會喚醒一個在等待中的線程,當然執行方法的線程必須釋放鎖,被喚醒的線程才能得到鎖
怎樣才能規定進入一段代碼的線程數
這里我們使用信號量,在外面定義Semaphore semp = new Semaphore(10);,這樣簡單的設定了5個線程
在run()方法中semp.acquire();表示獲得了10個中的其中一個許可證
當你的工作代碼完成時,依舊在run()方法的后面寫上semp.release();,許可證被釋放(跟鎖一個道理)
我們知道讀不會響應數據,寫會影響數據
所以我們真正操作的時候要求只讀的那些線程可以一起執行,不用同步操作
而與寫有關的線程全部要同步,以保護數據的安全
那么我們怎樣才能做到只讀線程不用同步呢
這里需要用到讀寫鎖,下面演示一段讀寫鎖的簡單例子:
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(); } } //寫 public void handleWrite(Lock lock,int index) throws InterruptedException { try { lock.lock(); Thread.sleep(5000); value=index; System.out.println("寫完成"); }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(); } }
執行結果可以發現,對于只讀的方法,我們給予readLock鎖,而寫的方法給予writeLock鎖
如此這般,只讀的方法可以并行,而讀寫只能串行
倒計時器用來控制線程等待,它可以讓一個線程等待直到倒計時結束,再開始執行
還是通過實例來讓大家知道什么是倒計時器,并且它能有什么作用
這個例子的需求是:要在主線程之前完成之個類線程才能繼續主線程:
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); //創建一個固定大小為10的線程池 for (int i=0;i<10;i++){ executorService.submit(countDownLatchThread); } countDownLatch.await(); System.out.println("10個任務完成"); executorService.shutdown(); } }
countDownLatch.await();:讓主線程進入等待,等待所有10個線程完成
countDownLatch.countDown();:說明這個線程已完成,并進入統計
線程池和數據庫連接池一樣,事先準備好線程,當程序需要線程時,調用線程池中空閑的線程,當工作線程工作完畢時,重新放回線程池
JDK提供了一套Executor框架,本質是線程池。
框架中的成員變量在java.util.concurrent包中,是JDK并發包的核心類
其中ThreadPoolExecutor實現了Executor接口,任何Runnable都可以被ThreadPoolExecutor線程池調度
Executor提供了各種類型的線程池,主要有以下工廠方法創建不同的線程池:
newFixedThreadPool()方法:返回一個固定線程數量的線程池,當一個新任務提交時,如果有空閑線程,立即執行,如果沒有空閑線程,新任務會被放在一個等待隊列中去等待空閑線程
newCachedThreadPool()方法:返回一個根據實際情況調整線程數量的線程池,
newSingleThreadScheduledExecutor()方法:返回一個對象,可放線程數量為1,但是這個線程池拓展了計劃任務功能,可以規定執行時間、周期性等等
上面這些線程池的源碼其實都是用ThreadPoolExecutor實現:
我們來看下ThreadPoolExecutor的構造函數:
public ThreadPoolExecutor(int corePoolSize, //指定線程池中線程的數量 int maximumPoolSize,//指定線程池中最大線程數量 long keepAliveTime, //當線程池中線程數量超過corePoolSize時,多余線程的存活時間 TimeUnit unit, //keepAliveTime的單位 BlockingQueueworkQueue, //等待任務隊列,被提交都是尚未執行的任務 ThreadFactory threadFactory, //線程工廠,用于創建線程,一般默認 RejectedExecutionHandler handler //拒絕策略,當任務太多時,如何拒絕任務 }
下面我來講講BlockingQueue:
在ThreadPoolExecutor構造器中,有以下幾種BlockingQueue:
1.直接提交隊列:有SynchronousQueue對象提供,提交的任務如果沒有空閑線程嘗試新建線程,如果線程數量已達到最大,則執行拒絕策略
2.有界的任務隊列:使用ArrayBlockingQueue對象實現,構造器帶一個任務的容量參數,若等待隊列已滿,總線程不大于maximumPoolSize的前提下,創建新的線程執行任務,若大于,則執行拒絕策略
3.無界的任務隊列:使用LinkedBlockingQueue來實現,和有界相比,除非系統資源耗盡,否則無界的任務隊列不存在任務入隊失敗的情況
4.有限任務隊列:通過PriorityBlockingQueue實現,可以控制任務的執行先后順序
再來看newFixedThreadPool(),它使用了無界任務隊列,并且corePoolSize和maximumPoolSize一樣大,因為對于固定大小的線程池,不存在線程數量的動態變化,當任務提交非常頻繁時,可能會耗盡系統資源
而newCachedThreadPool()方法返回corePoolSize為0,maximumPoolSize無限大的線程池,使用了SynchronousQueue隊列,當任務執行完畢后,由于corePoolSize為0,空閑線程會在指定時間(60s)回收
講完了BlockingQueue我們來講下RejectedExecutionHandler拒絕策略
JDK內置了四種拒絕策略:
1.AbortPolicy:直接拋出異常,阻止系統正常工作
2.CallerRunsPolicy:只要線程池沒有關閉,該策略直接調用工作線程運行當前被丟棄的任務
3.DiscardOledestPolicy:丟棄最老的一個等待任務,也就是即將被執行的一個任務,并嘗試再次提交當前任務
4.DiscardPolicy:默默地丟棄無法處理的任務,如果運行任務丟失,這是最好的一個策略
當然我們也可以自己寫拒絕策略
下面我來寫一個打印出被拒絕的策略(而不是選擇拋異常,因為拋異常我們還要去捕捉異常,如果沒有捕捉到會導致系統奔潰)
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 來看下運行結果:
1511833277724線程ID:13 1511833277771線程ID:10 1511833277771線程ID:14 1511833277771線程ID:12 等待線程被拒絕 等待線程被拒絕 1511833277833線程ID:13 1511833277833線程ID:11可以發現,我們自定義的線程池和拒絕策略完美的執行了
并發高效容器下面我介紹給大家幾個非常好用的工具類(當然都是線程安全的)
1.ConcurrentHashMap:這是一個高效的hashMap
2.CopyOnwriteArrayList:在讀多寫少的場合這個list非常好用,遠勝與Vector
3.ConCurrentLinkedQueue:高效的并發隊列,使用鏈表實現,可以看做是一個線程安全的LinkedList
4.BlockingQueue:這個接口上面說過,表示阻塞隊列,非常適合用于作為數據共享的通道
5.ConcurrentSkipListMap:這是一個Map,使用跳表的數據結構進行快速的查找如果并不追求高效,我們可以使用Collections類幫助把線程不安全的容器轉換為線程安全
如將HashMap轉換為線程安全:Map map = Collections.synchronizedMap(new HashMap()); 當然可以使用CAS操作來替代synchronized
今天就先到這里,大家可以看看這些內容的拓展
記得點關注看更新,謝謝閱讀
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70665.html
摘要:今天就先到這里,大家可以看看這些內容的拓展記得點關注看更新,謝謝閱讀 前言 這是一個長篇博客,希望大家關注我并且一起學習java高并發廢話不多說,直接開始 并行和并發 并行:多個線程同時處理多個任務并發:多個線程處理同個任務,不一定要同時 下面用圖來描述并行和并發的區別:(實現和虛線表示兩個不同的線程) showImg(https://segmentfault.com/img/bVYT...
摘要:可以用代替可以用代替定義的對象的值是不可變的今天就先到這里,大家可以看看這些內容的拓展記得點關注看更新,謝謝閱讀 前言 java高并發第二篇講的是java線程的基礎依舊不多說廢話 線程和進程 進程是操作系統運行的基礎,是一個程序運行的實體,windows上打開任務管理器就能看到進程線程是輕量級的進程,是程序執行的最小單位,是在進程這個容器下進行的 線程基本操作 新建一個線程類有兩種方式...
摘要:前言這篇主要來講解多線程中一個非常經典的設計模式包括它的基礎到拓展希望大家能夠有所收獲生產者消費者模式簡述此設計模式中主要分兩類線程生產者線程和消費者線程生產者提供數據和任務消費者處理數據和任務該模式的核心就是數據和任務的交互點共享內存緩 前言 這篇主要來講解多線程中一個非常經典的設計模式包括它的基礎到拓展希望大家能夠有所收獲 生產者-消費者模式簡述 此設計模式中主要分兩類線程:生產者...
摘要:前言本篇主要講解如何去優化鎖機制或者克服多線程因為鎖可導致性能下降的問題線程變量有這樣一個場景,前面是一大桶水,個人去喝水,為了保證線程安全,我們要在杯子上加鎖導致大家輪著排隊喝水,因為加了鎖的杯子是同步的,只能有一個人拿著這個唯一的杯子喝 前言 本篇主要講解如何去優化鎖機制或者克服多線程因為鎖可導致性能下降的問題 ThreadLocal線程變量 有這樣一個場景,前面是一大桶水,10個...
閱讀 1349·2021-09-28 09:43
閱讀 4116·2021-09-04 16:41
閱讀 1918·2019-08-30 15:44
閱讀 3729·2019-08-30 15:43
閱讀 776·2019-08-30 14:21
閱讀 2037·2019-08-30 11:00
閱讀 3320·2019-08-29 16:20
閱讀 1923·2019-08-29 14:21