国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 淺析Java中如何實現線程之間通信

淺析Java中如何實現線程之間通信

2020-09-12 15:37wingjay Java教程

本篇文章主要介紹了淺析Java中如何實現線程之間通信。針對 Java 的線程間通信進行了大致的講解,有興趣的可以了解一下

正常情況下,每個子線程完成各自的任務就可以結束了。不過有的時候,我們希望多個線程協同工作來完成某個任務,這時就涉及到了線程間通信了。

本文涉及到的知識點:thread.join(), object.wait(), object.notify(), CountdownLatch, CyclicBarrier, FutureTask, Callable 等。

下面我從幾個例子作為切入點來講解下 Java 里有哪些方法來實現線程間通信。

  1. 如何讓兩個線程依次執行?
  2. 那如何讓兩個線程按照指定方式有序交叉運行呢?
  3. 四個線程 A B C D,其中 D 要等到 A B C 全執行完畢后才執行,而且 A B C 是同步運行的
  4. 三個運動員各自準備,等到三個人都準備好后,再一起跑
  5. 子線程完成某件任務后,把得到的結果回傳給主線程

如何讓兩個線程依次執行?

假設有兩個線程,一個是線程 A,另一個是線程 B,兩個線程分別依次打印 1-3 三個數字即可。我們來看下代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static void demo1() {
 Thread A = new Thread(new Runnable() {
  @Override
  public void run() {
   printNumber("A");
  }
 });
 
 Thread B = new Thread(new Runnable() {
  @Override
  public void run() {
   printNumber("B");
  }
 });
 
 A.start();
 B.start();
}

其中的 printNumber(String) 實現如下,用來依次打印 1, 2, 3 三個數字:

?
1
2
3
4
5
6
7
8
9
10
11
private static void printNumber(String threadName) {
 int i=0;
 while (i++ < 3) {
  try {
   Thread.sleep(100);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  System.out.println(threadName + " print: " + i);
 }
}

這時我們得到的結果是:

B print: 1
A print: 1
B print: 2
A print: 2
B print: 3
A print: 3

可以看到 A 和 B 是同時打印的。

那么,如果我們希望 B 在 A 全部打印完后再開始打印呢?我們可以利用 thread.join() 方法,代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static void demo2() {
 Thread A = new Thread(new Runnable() {
  @Override
  public void run() {
   printNumber("A");
  }
 });
 
 Thread B = new Thread(new Runnable() {
  @Override
  public void run() {
   System.out.println("B 開始等待 A");
   try {
    A.join();
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
 
   printNumber("B");
  }
 });
 
 B.start();
 A.start();
}

得到的結果如下:

B 開始等待 A
A print: 1
A print: 2
A print: 3

B print: 1
B print: 2
B print: 3

所以我們能看到 A.join() 方法會讓 B 一直等待直到 A 運行完畢。

那如何讓兩個線程按照指定方式有序交叉運行呢?

還是上面那個例子,我現在希望 A 在打印完 1 后,再讓 B 打印 1, 2, 3,最后再回到 A 繼續打印 2, 3。這種需求下,顯然 Thread.join() 已經不能滿足了。我們需要更細粒度的鎖來控制執行順序。

這里,我們可以利用 object.wait() object.notify() 兩個方法來實現。代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * A 1, B 1, B 2, B 3, A 2, A 3
 */
private static void demo3() {
 Object lock = new Object();
 
 Thread A = new Thread(new Runnable() {
  @Override
  public void run() {
   synchronized (lock) {
    System.out.println("A 1");
    try {
     lock.wait();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
 
    System.out.println("A 2");
    System.out.println("A 3");
   }
 
  }
 });
 
 Thread B = new Thread(new Runnable() {
  @Override
  public void run() {
   synchronized (lock) {
    System.out.println("B 1");
    System.out.println("B 2");
    System.out.println("B 3");
 
    lock.notify();
   }
  }
 });
 
 A.start();
 B.start();
}

打印結果如下:

A 1
A waiting...

B 1
B 2
B 3
A 2
A 3

正是我們要的結果。

那么,這個過程發生了什么呢?

  1. 首先創建一個 A 和 B 共享的對象鎖 lock = new Object();
  2. 當 A 得到鎖后,先打印 1,然后調用 lock.wait() 方法,交出鎖的控制權,進入 wait 狀態;
  3. 對 B 而言,由于 A 最開始得到了鎖,導致 B 無法執行;直到 A 調用 lock.wait() 釋放控制權后, B 才得到了鎖;
  4. B 在得到鎖后打印 1, 2, 3;然后調用 lock.notify() 方法,喚醒正在 wait 的 A;
  5. A 被喚醒后,繼續打印剩下的 2,3。

為了更好理解,我在上面的代碼里加上 log 方便讀者查看。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private static void demo3() {
 Object lock = new Object();
 Thread A = new Thread(new Runnable() {
  @Override
  public void run() {
   System.out.println("INFO: A 等待鎖");
   synchronized (lock) {
    System.out.println("INFO: A 得到了鎖 lock");
    System.out.println("A 1");
    try {
     System.out.println("INFO: A 準備進入等待狀態,放棄鎖 lock 的控制權");
     lock.wait();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("INFO: 有人喚醒了 A, A 重新獲得鎖 lock");
    System.out.println("A 2");
    System.out.println("A 3");
   }
 
  }
 });
 Thread B = new Thread(new Runnable() {
  @Override
  public void run() {
   System.out.println("INFO: B 等待鎖");
   synchronized (lock) {
    System.out.println("INFO: B 得到了鎖 lock");
    System.out.println("B 1");
    System.out.println("B 2");
    System.out.println("B 3");
 
    System.out.println("INFO: B 打印完畢,調用 notify 方法");
    lock.notify();
   }
  }
 });
 A.start();
 B.start();
}

打印結果如下:

INFO: A 等待鎖
INFO: A 得到了鎖 lock
A 1
INFO: A 準備進入等待狀態,調用 lock.wait() 放棄鎖 lock 的控制權
INFO: B 等待鎖
INFO: B 得到了鎖 lock
B 1
B 2
B 3
INFO: B 打印完畢,調用 lock.notify() 方法
INFO: 有人喚醒了 A, A 重新獲得鎖 lock
A 2
A 3

四個線程 A B C D,其中 D 要等到 A B C 全執行完畢后才執行,而且 A B C 是同步運行的

最開始我們介紹了 thread.join(),可以讓一個線程等另一個線程運行完畢后再繼續執行,那我們可以在 D 線程里依次 join A B C,不過這也就使得 A B C 必須依次執行,而我們要的是這三者能同步運行。

或者說,我們希望達到的目的是:A B C 三個線程同時運行,各自獨立運行完后通知 D;對 D 而言,只要A B C 都運行完了,D 再開始運行。針對這種情況,我們可以利用 CountdownLatch 來實現這類通信方式。它的基本用法是:

  1. 創建一個計數器,設置初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
  2. 等待線程里調用 countDownLatch.await() 方法,進入等待狀態,直到計數值變成 0;
  3. 其他線程里,調用 countDownLatch.countDown() 方法,該方法會將計數值減小 1;
  4. 其他線程countDown() 方法把計數值變成 0 時,等待線程 里的 countDownLatch.await() 立即退出,繼續執行下面的代碼。

實現代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private static void runDAfterABC() {
 int worker = 3;
 CountDownLatch countDownLatch = new CountDownLatch(worker);
 
 new Thread(new Runnable() {
  @Override
  public void run() {
   System.out.println("D is waiting for other three threads");
   try {
    countDownLatch.await();
    System.out.println("All done, D starts working");
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
 
  }
 }).start();
 
 for (char threadName='A'; threadName <= 'C'; threadName++) {
  final String tN = String.valueOf(threadName);
  new Thread(new Runnable() {
   @Override
   public void run() {
    System.out.println(tN + " is working");
    try {
     Thread.sleep(100);
    } catch (Exception e) {
     e.printStackTrace();
    }
 
    System.out.println(tN + " finished");
    countDownLatch.countDown();
   }
  }).start();
 }
}

下面是運行結果:

D is waiting for other three threads
A is working
B is working
C is working

A finished
C finished
B finished
All done, D starts working

其實簡單點來說,CountDownLatch 就是一個倒計數器,我們把初始計數值設置為3,當 D 運行時,先調用 countDownLatch.await() 檢查計數器值是否為 0,若不為 0 則保持等待狀態;當A B C 各自運行完后都會利用countDownLatch.countDown(),將倒計數器減 1,當三個都運行完后,計數器被減至 0;此時立即觸發 D await() 運行結束,繼續向下執行。

因此,CountDownLatch 適用于一個線程去等待多個線程的情況。

三個運動員各自準備,等到三個人都準備好后,再一起跑

上面是一個形象的比喻,針對線程 A B C 各自開始準備,直到三者都準備完畢,然后再同時運行。也就是要實現一種線程之間互相等待的效果,那應該怎么來實現呢?

上面的 CountDownLatch 可以用來倒計數,但當計數完畢,只有一個線程的 await() 會得到響應,無法讓多個線程同時觸發。

為了實現線程間互相等待這種需求,我們可以利用 CyclicBarrier 數據結構,它的基本用法是:

  1. 先創建一個公共 CyclicBarrier 對象,設置同時等待的線程數,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  2. 這些線程同時開始自己做準備,自身準備完畢后,需要等待別人準備完畢,這時調用 cyclicBarrier.await(); 即可開始等待別人;
  3. 當指定的同時等待的線程數都調用了 cyclicBarrier.await();時,意味著這些線程都準備完畢好,然后這些線程才同時繼續執行。

實現代碼如下,設想有三個跑步運動員,各自準備好后等待其他人,全部準備好后才開始跑:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static void runABCWhenAllReady() {
 int runner = 3;
 CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
 
 final Random random = new Random();
 for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
  final String rN = String.valueOf(runnerName);
  new Thread(new Runnable() {
   @Override
   public void run() {
    long prepareTime = random.nextInt(10000) + 100;
    System.out.println(rN + " is preparing for time: " + prepareTime);
    try {
     Thread.sleep(prepareTime);
    } catch (Exception e) {
     e.printStackTrace();
    }
 
    try {
     System.out.println(rN + " is prepared, waiting for others");
     cyclicBarrier.await(); // 當前運動員準備完畢,等待別人準備好
    } catch (InterruptedException e) {
     e.printStackTrace();
    } catch (BrokenBarrierException e) {
     e.printStackTrace();
    }
 
    System.out.println(rN + " starts running"); // 所有運動員都準備好了,一起開始跑
   }
  }).start();
 }
}

打印的結果如下:

A is preparing for time: 4131
B is preparing for time: 6349
C is preparing for time: 8206

A is prepared, waiting for others

B is prepared, waiting for others

C is prepared, waiting for others

C starts running
A starts running
B starts running

子線程完成某件任務后,把得到的結果回傳給主線程

實際的開發中,我們經常要創建子線程來做一些耗時任務,然后把任務執行結果回傳給主線程使用,這種情況在 Java 里要如何實現呢?

回顧線程的創建,我們一般會把 Runnable 對象傳給 Thread 去執行。Runnable定義如下:

?
1
2
3
public interface Runnable {
 public abstract void run();
}

可以看到 run() 在執行完后不會返回任何結果。那如果希望返回結果呢?這里可以利用另一個類似的接口類 Callable

?
1
2
3
4
5
6
7
8
9
10
@FunctionalInterface
public interface Callable<V> {
 /**
  * Computes a result, or throws an exception if unable to do so.
  *
  * @return computed result
  * @throws Exception if unable to compute a result
  */
 V call() throws Exception;
}

可以看出 Callable 最大區別就是返回范型 V 結果。

那么下一個問題就是,如何把子線程的結果回傳回來呢?在 Java 里,有一個類是配合 Callable 使用的:FutureTask,不過注意,它獲取結果的 get 方法會阻塞主線程。

舉例,我們想讓子線程去計算從1加到100,并把算出的結果返回到主線程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static void doTaskWithResultInWorker() {
 Callable<Integer> callable = new Callable<Integer>() {
  @Override
  public Integer call() throws Exception {
   System.out.println("Task starts");
   Thread.sleep(1000);
   int result = 0;
   for (int i=0; i<=100; i++) {
    result += i;
   }
   System.out.println("Task finished and return result");
   return result;
  }
 };
 
 FutureTask<Integer> futureTask = new FutureTask<>(callable);
 new Thread(futureTask).start();
 
 
 try {
  System.out.println("Before futureTask.get()");
  System.out.println("Result: " + futureTask.get());
  System.out.println("After futureTask.get()");
 } catch (InterruptedException e) {
  e.printStackTrace();
 } catch (ExecutionException e) {
  e.printStackTrace();
 }
}

打印結果如下:

Before futureTask.get()

Task starts
Task finished and return result

Result: 5050
After futureTask.get()

可以看到,主線程調用 futureTask.get() 方法時阻塞主線程;然后 Callable 內部開始執行,并返回運算結果;此時 futureTask.get() 得到結果,主線程恢復運行。

這里我們可以學到,通過 FutureTask Callable 可以直接在主線程獲得子線程的運算結果,只不過需要阻塞主線程。當然,如果不希望阻塞主線程,可以考慮利用 ExecutorService,把 FutureTask 放到線程池去管理執行。

小結

多線程是現代語言的共同特性,而線程間通信、線程同步、線程安全是很重要的話題。本文針對 Java 的線程間通信進行了大致的講解,后續還會對線程同步、線程安全進行講解。希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://www.jianshu.com/p/42a52d53152d#

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 91久久极品| 国内精品久久久久久 | 国产精品美乳一区二区免费 | 久久先锋 | 久久国产精品久久久久久电车 | 日韩成人在线电影 | 黄色在线网站 | 色吧网站 | 在线亚洲电影 | 久久亚洲精品中文字幕 | 国产精品成人在线观看 | 亚洲激情一区二区三区 | 直接看av的网站 | 香蕉大人久久国产成人av | 欧美资源在线 | 成人av网页 | 亚洲激情视频 | 中文字幕 国产精品 | 亚洲a网站 | 男人天堂网址 | 在线免费观看av的网站 | 中文字幕在线免费看 | 日韩精品在线观看视频 | 久久久久久久一区 | 国产福利一区二区 | 免费视频黄 | 国产精品成人一区二区三区 | 欧美成人精品欧美一级私黄 | 亚洲一区中文字幕 | 久久这里只有精品8 | 天天插天天操 | 国产精品成人一区二区三区夜夜夜 | 中国黄色三级毛片 | 99久久国语露脸精品对白 | 亚洲视频在线播放 | 亚洲精品一区二区三区在线观看 | 亚洲精品日韩激情在线电影 | 亚洲国产精品一区 | 超碰天天 | 亚洲欧美制服诱惑 | 久久久久9999国产精品 |