併發問題和主執行緒等待所有子執行緒執行完畢再執行
阿新 • • 發佈:2019-01-24
問題引出:
我們對資料庫的操作是一個耗時過程,假如我們需要讓資料庫批量操作完成之後,再跳轉到另外一個頁面,注意:是批量操作完成之後再跳轉。
分析:以上需求我們遇到2個難點,
第一個難點是怎麼控制併發問題,
第二個難點是怎麼使主執行緒等待所有子執行緒完成之後再執行。
首先,我們先解決併發問題,其實,在jdk1.5的時候,java大牛Doug Lea執行緒已經解決了這個問題,我們今天就借用Doug Lea先生的API來解決這個問題。如下是詳細解釋和原始碼:
<span style="font-size:12px;">package com.bzjy.thread; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicInteger; public class ThreadTest { private static Semaphore semaphore = new Semaphore(5);// 設定併發訊號量為5 private static AtomicInteger atomicInteger = new AtomicInteger(0);// 宣告原子操作整數,初始化為9 public static void main(String[] args) { <span style="color:#009900;">// 首先介紹一個併發工具包,Semphore(訊號量) // 這裡我們需要用到它的代參構造Semaphore semaphore = new Semaphore(permits); // 引數permits的意思是同時可以存在多少個訊號,或者說是同時可以併發多少個訊號 // 比如說:我有100個執行緒,同時併發只併發5個,則設定permits為5 // 說到這裡又涉及到一個問題就是,</span><span style="color:#ff0000;">執行緒是越多越好麼</span><span style="color:#009900;">? // 答案自然是否定的,這裡Google官方給出的最佳併發數是當前伺服器核心數+1 // 也就是說,pertits = cpu(核心數) + 1;假如是4核的則推薦最佳併發數是5 // 因為我的電腦是4核的,所以這裡就舉例併發為5,這裡不再進行程式碼的封裝, // 只是舉一個簡單的例子</span> //Semaphore semaphore = new Semaphore(5);// 設定併發訊號量為5 // 這裡我們已經建立好併發數了,那麼我們怎麼使用呢? // 這裡介紹兩個核心方法, // 第一個:semaphore.acquire(); 獲取訊號或者說獲取一把鎖 // 第二個:semaphore.release(); 釋放訊號或者說釋放一把鎖 // 示例程式碼如下: for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { // <span style="color:#ff0000;">注意</span>:在剛進入方法就需要獲得一把鎖,<span style="color:#ff0000;">再次強調,是剛進入方法就獲得一把鎖</span> try { semaphore.acquire();// 獲取一把鎖,因為是在匿名內部類中使用,所以需要將其宣告為成員變數 // 這裡我想對執行緒進行計數,但是是多執行緒併發,所以不能直接用i++來計數, // 因此我們需要進行原子操作,為此,我們再次介紹一個工具包,“原子”,Atomic // 這裡我們使用整形的AtomicInteger,因為需要在內部類中使用,所以宣告為成員變數,設定初始化為0 int current = atomicInteger.getAndIncrement(); System.out.println("第" + current +"進來了");// 列印日誌 Thread.sleep(1000);// 讓當前執行緒睡1秒,模仿耗時操作 System.out.println("第" + current +"出去了"); semaphore.release();// 釋放一把鎖,<span style="color:#ff0000;">必須釋放,必須是方法最後一步</span> } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } }</span>
如此,我們第一個問題就解決了,註釋寫的很詳細,相信0基礎也能看懂,程式碼100%執行通過,這裡不再貼執行結果圖片。
好,第一個問題解決了,呢麼我們怎麼控制使主執行緒等待所有子執行緒執行結束之後再執行呢?
這裡我們需要再次介紹一工具類,示例程式碼如下:有詳細註釋
/** * 注意:類很多程式碼在上面已經書寫過詳細註釋,這裡寫過的註釋不再書寫 * @author xiyang * */ public class ThreadWaitTest { private static Semaphore semaphore = new Semaphore(5);// 設定併發訊號量為5 private static AtomicInteger atomicInteger = new AtomicInteger(0);// 宣告原子操作整數,初始化為9 // 這句程式碼詳細註釋見主函式,另外我這裡為了方便直接設定引數為100,實際開發可以根據需求例項化 private static CountDownLatch cdl = new CountDownLatch(100); public static void main(String[] args) throws InterruptedException { // 因為我們需要處理主執行緒等待所有子執行緒執行結束之後再執行, // 因此為了方便,科學,我們再次介紹一個執行緒併發工具包CountDownLatch, // 同樣,我們需要它的一個有參構造,CountDownLatch cdl = new CountDownLatch(count); // 注意,這裡引數,必須和執行緒池的最大數一致,也就是說,這裡的引數必須是你執行緒執行的最大數 // 比如我這裡要執行100個執行緒,併發5個,那麼,這裡的count==100 // 因為我們需要在內部類中使用,所以宣告為成員變數 for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { // 注意:在剛進入方法就需要獲得一把鎖,再次強調,是剛進入方法就獲得一把鎖 try { semaphore.acquire();// 獲取一把鎖,因為是在匿名內部類中使用,所以需要將其宣告為成員變數 int current = atomicInteger.getAndIncrement(); System.out.println("第" + current +"進來了");// 列印日誌 Thread.sleep(1000);// 讓當前執行緒睡1秒,模仿耗時操作 System.out.println("第" + current +"出去了"); <span style="color:#ff0000;">cdl.countDown();</span>// 此方法的意思是執行緒數量-1,要在當前執行緒執行完畢之後書寫,或者說子執行緒核心程式碼執行完畢之後 semaphore.release();// 釋放一把鎖,必須釋放,必須是方法最後一步 } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } <span style="color:#ff0000;">cdl.await();</span>// 主執行緒等待,當執行緒數量為0時主執行緒執行 System.out.println("主執行緒執行"); // 如此就解決了執行緒併發問題和主執行緒等待所有子執行緒執行完畢之後執行 } }
如此以上兩個需求就解決了,這裡只是示例,大家可以根據具體需求具體操作,最好是可以進行封裝呼叫,最好