在多線程中線程的執行順序是依靠哪一個線程先得到到CUP的執行權誰就先執行,雖說能夠經過線程的優先權進行設置,可是他只是獲取CUP執行權的機率高點,可是也不必定必須先執行。在這種狀況下如何保證線程按照必定的順序進行執行,今天就來一個大總結,分別介紹一下幾種方式。
1、經過Object的wait和notify
2、經過Condition的awiat和signal
3、經過一個阻塞隊列
4、經過兩個阻塞隊列
5、經過SynchronousQueue
6、經過線程池的Callback回調
7、經過同步輔助類CountDownLatch
8、經過同步輔助類CyclicBarrierios
寫一個測試了Test,加上main方法,在寫一個內部類Man進行測試。main方法以下,他進行建立兩個線程,傳進去Runnable對象。面試
public static boolean flag = false; public static int num = 0; public static void main(String[] args) { Man man = new Man(); new Thread(() -> { man.getRunnable1(); }).start(); new Thread(() -> { man.getRunnable2(); }).start(); }
getRunnable1和getRunnable2分別表示兩個須要執行的任務,在兩個線程中進行,方法1用於數據的生產,方法二用於數據的獲取,數據的初始值爲num = 0,爲了保證生產和獲取平衡須要使用wait和notify方法,這兩個方法的使用必須是要加鎖的,所以使用synchronized進行加鎖使用,爲了演示這個效果,咱們加上一個sleep方法模擬處理時間,以下:緩存
public static class Man { public synchronized void getRunnable1() { for (int i = 0; i < 20; i++) { while (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生產出:" + (++num) + "個"); flag = true; notify(); } } public synchronized void getRunnable2() { for (int i = 0; i < 20; i++) { while (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //模擬加載時間 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("取出出:" + (num--) + "個"); System.out.println("------------------"); flag = false; notify(); } } }
分析它的加載流程,從方法1進行分析,因爲flag的初始條件爲false,因此方法1不進入等待,直接進行生產,生產完成成以後,更新flag的值爲true,同時notify下一個方法2的wait方法,使其變爲喚醒狀態。這時候因爲方法1加鎖了,沒法執行方法1其餘部分,當方法1執行完畢,方法1纔有可能執行,可是方法1的flag已經爲true,進入到wait裏面又處於阻塞狀態,因此這時候只能執行方法2了。因爲方法2被喚醒了,阻塞解除,接下來就獲取數據,當獲取完畢又再次讓flag變爲false,notify方法1解除阻塞,再次執行方法1,就這樣不斷的循環,保證了不一樣線程的有序執行,直到程序終止。網絡
運行效果以下:
多線程
上面第一個的實現是一個阻塞,一個等待的方式保證線程有序的執行,可是不能進行兩個線程之間進行通訊,而接下來介紹的Condition就具有這樣的功能。要獲取Condition對象首先先得獲取Lock對象,他是在jdk1.5以後增長的,比synchronized性能更好的一種鎖機制。和上面的相似,拷貝一份代碼,看看main方法:框架
public static boolean flag = false; public static int num = 0; public static void main(String[] args) { Man man = new Man(); new Thread(() -> { man.getRunnable1(); }).start(); new Thread(() -> { man.getRunnable2(); }).start(); }
狀況和第一個實現方法分析一致,這裏不重複了。主要看內部類Man中的方法1和方法2。先手建立鎖對象,把synchronized改成使用Lock加鎖,其次經過Lock建立Condition對象,替換掉Object類的wait方法爲Condition的await方法,最後換掉notify方法爲signal方法便可,執行原理和上面分析一致,代碼以下:ide
public static class Man { public static ReentrantLock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); public void getRunnable1() { lock.lock(); try { for (int i = 0; i < 20; i++) { while (flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生產出:" + (++num) + "個"); flag = true; condition.signal(); } } finally { lock.lock(); } } public void getRunnable2() { lock.lock(); try { for (int i = 0; i < 20; i++) { while (!flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("取出出:" + (num--) + "個"); System.out.println("------------------"); flag = false; condition.signal(); } } finally { lock.unlock(); } } }
執行結果以下:
性能
這是個人iOS開發交流羣:519832104無論你是小白仍是大牛歡迎入駐,能夠一塊兒分享經驗,討論技術,共同窗習成長!
另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,進羣便可自行下載!
學習點擊此處,當即與iOS大牛交流學習
上面的兩個方法實現起來代碼比較繁瑣,若是經過阻塞隊列來實現會更加簡潔,這裏採用經常使用的容量爲64的ArrayBlockingQueue來實現。main方法以下:測試
public static void main(String[] args) { Man man = new Man(); new Thread(() -> { man.getRunnable1(); }).start(); new Thread(() -> { man.getRunnable2(); }).start(); }
主要來看Man中的方法1和方法2,方法1中生產數據,這裏把生產的數據存進隊列裏面,同時方法2進行取數據,若是方法1放滿了或者方法2取完了就會被阻塞住,等待方法1生產好了或者方法2取出了,而後再進行。代碼以下:
public static class Man { ArrayBlockingQueue queue = new ArrayBlockingQueue<Integer>(64); public void getRunnable1() { for (int i = 0; i < 8; i++) { System.out.println("生產出:" + i + "個"); try { queue.put(i); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("---------------生產完畢-----------------"); } public void getRunnable2() { for (int i = 0; i < 8; i++) { try { int num = (int) queue.take(); System.out.println("取出出:" + num); } catch (InterruptedException e) { e.printStackTrace(); } } } }
很明顯使用阻塞隊列代碼精煉了不少,在這還能夠發現這個阻塞隊列是具備緩存功能的,想不少Android中網絡訪問框架內部就是使用這個進行緩存的,例如Volley、Okhttp等等。
運行效果以下:
使用一個阻塞隊列可以實現線程同步的功能,兩個阻塞隊列也能夠實現線程同步。原理是ArrayBlockingQueue他是具備容量的,若是把他的容量定位1則意味着他只能放進去一個元素,第二個方進行就會就會被阻塞。按照這個原理進行來實現,定義兩個容量爲1的阻塞隊列ArrayBlockingQueue,一個存放數據,另外一個用於控制次序。main方法和上面一致,主要來看看Man類中的兩個方法:
static class Man { //數據的存放 ArrayBlockingQueue queue1 = new ArrayBlockingQueue<Integer>(1); //用於控制程序的執行 ArrayBlockingQueue queue2 = new ArrayBlockingQueue<Integer>(1); { try { //queue2放進去一個元素,getRunnable2阻塞 queue2.put(22222); } catch (InterruptedException e) { e.printStackTrace(); } } public void getRunnable1() { new Thread(() -> { for (int j = 0; j < 20; j++) { try { //queue1放進一個元素,getRunnable1阻塞 queue1.put(j); System.out.println("存放 線程名稱:" + Thread.currentThread().getName() + "-數據爲-" + j); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } try { //queue2取出元素,getRunnable2進入 queue2.take(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } public void getRunnable2() { new Thread(() -> { for (int j = 0; j < 20; j++) { try { //queue2放進一個元素,getRunnable2阻塞 queue2.put(22222); } catch (InterruptedException e) { e.printStackTrace(); } try { //queue1放進一個元素,getRunnable1進入 int i = (int) queue1.take(); System.out.println("獲取 線程名稱:" + Thread.currentThread().getName() + "-數據爲-" + i); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
再次提醒queue2用於控制程序的執行次序,並沒有實際含義。最後看看運行效果,存一個、取一個很清晰,以下:
SynchronousQueue不一樣於通常的數據等線程,而是線程等待數據,他是一個沒有數據緩衝的BlockingQueue,生產者線程對其的插入操做put必須等待消費者的移除操做take,反過來也同樣。經過這一特性來實現一個多線程同步問題的解決方案,代碼以下:
/** * 使用阻塞隊列SynchronousQueue * offer將數據插入隊尾 * take取出數據,若是沒有則阻塞,直到有數據在獲取到 */ public static void test() { SynchronousQueue queue = new SynchronousQueue(); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { @Override public void run() { try { Thread.sleep(5000); queue.offer(9); } catch (InterruptedException e) { e.printStackTrace(); } } }); try { int take = (int) queue.take(); System.out.println(take); } catch (InterruptedException e) { e.printStackTrace(); } }
子線程中進行設置數據,而主線程獲取數據,若是子線程沒執行完畢,子線程沒有執行完畢主線程就會被阻塞住不能執行下一步。
在線程的建立中,有一種建立方法能夠返回線程結果,就是callback,他能返回線程的執行結果,經過子線程返回的結果進而在主線程中進行操做,也是一種同步方法,這種同步在Android中特別適用,例如Android中的AsyncTask源碼中任務的建立部分。代碼以下:
private static void test() { ExecutorService executorService = Executors.newFixedThreadPool(5); Future<Boolean> submit = executorService.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return false; } }); try { if (submit.get()) { System.out.println(true); } else { System.out.println(false); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
CountDownLatch是一個同步的輔助類,容許一個或多個線程,等待其餘一組線程完成操做,再繼續執行。他類其實是使用計數器的方式去控制的,在建立的時候傳入一個int數值每當咱們調用countDownt()方法的時候就使得這個變量的值減1,而對於await()方法則去判斷這個int的變量的值是否爲0,是則表示全部的操做都已經完成,不然繼續等待。能夠理解成倒計時鎖。
public class Test7 { public static void main(String[] args) { //啓動兩個線程,分別執行完畢以後再執行主線程 CountDownLatch countDownLatch = new CountDownLatch(2); //線程1執行 Thread thread1 = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "線程執行完畢"); countDownLatch.countDown(); }); //線程2執行 Thread thread2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "線程執行完畢"); countDownLatch.countDown(); }); thread1.start(); thread2.start(); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } //執行主線程 System.out.println("主線程執行完畢"); } }
結果以下:
CyclicBarrier是一個同步的輔助類,和上面的CountDownLatch比較相似,不一樣的是他容許一組線程相互之間等待,達到一個共同點,再繼續執行。可當作是個障礙,全部的線程必須到齊後才能一塊兒經過這個障礙。
public class Test8 { public static void main(String[] args) { //啓動兩個線程,分別執行完畢以後再執行主線程 CyclicBarrier barrier = new CyclicBarrier(2, () -> { //執行主線程 System.out.println("主線程執行完畢"); }); //線程1執行 Thread thread1 = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "線程執行完畢"); try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); //線程2執行 Thread thread2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "線程執行完畢"); try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); thread1.start(); thread2.start(); } }
運行結果: