<dl id="opymh"></dl>

<div id="opymh"></div>
      <div id="opymh"><tr id="opymh"></tr></div>

        <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

        <em id="opymh"></em>

        <em id="opymh"><ol id="opymh"></ol></em>

              頻道欄目
              首頁 > 程序開發 > 軟件開發 > Java > 正文
              java線程之間的通信方式實例講解
              2018-07-27 14:46:21         來源:nwpu_geeker的博客  
              收藏   我要投稿

              引言

              當多個線程需要協作來完成一件事情的時候,如何去等待其他線程執行,又如何當線程執行完去通知其他線程結束等待。

              線程與進程的區別

              進程可以獨立運行,它是系統進行資源分配和調度的獨立單位。

              線程是進程的一個實體,是CPU調度和分派的基本單位,比進程更小的獨立單位,它基本上不擁有系統資源。

              他們之間的聯系:

              一個線程屬于一個進程,而一個進程有多個線程,多個線程共享該進程的所有資源。

              區別:

              1.調度:線程作為CPU調度和分派的基本單位,進程擁有系統資源的獨立單位。

              2.并發性:進程之間并發運行,同一個進程的多個線程也能并發執行。

              3.擁有資源,進程是擁有系統資源的基本單位,線程不擁有資源,但能訪問進程的資源。

              線程之間的通信方式有哪些

              1.join

              2.共享變量(volatile、AtomicInteger)

              3.notify/wait

              4.lock/condition

              5.管道

              join

              首先,開啟兩個線程,分別打印123,線程A和線程B會不按套路打印。

              如果必須要線程A在線程B之前打印123呢?

              需要使用thread1.join();//我會等待線程1執行完成后再進行執行

              join的原理

              實際上join方法內部是通過wait實現的。

              上一段jdk源碼

              public final synchronized void join(long millis)
               throws InterruptedException {
                long base = System.currentTimeMillis();
                long now = 0;
              
                if (millis < 0) {
              throw new IllegalArgumentException("timeout value is negative");
                }
              
                if (millis == 0) {
              while (isAlive()) {
               wait(0);
              }
                } else {
              while (isAlive()) {
               long delay = millis - now;
               if (delay <= 0) {
                break;
               }
               wait(delay);
               now = System.currentTimeMillis() - base;
              }
                }
               }

              這個join的原理很簡單,前面那些if條件不管,主要看while循環里面的,while循環就是不斷去判斷this.isAlive的結果,用上面的例子,這個this就是joinThread。然后關鍵的代碼就是wait(delay);一個定時的wait。這個wait的對象也是this,就是joinThread。上面我們已經講了wait一定要在同步方法或者同步代碼塊中,源碼中join方法的修飾符就是一個synchronized,表明這是一個同步的方法。

              不要看調用wait是joinThread,是一個線程。但是真正因為wait進入阻塞狀態的,是持有對象監視器的線程,這里的對象監視器是joinThread,持有他的是main線程,因為在main線程中執行了join這個同步方法。

              所以main線程不斷的wait,直到調用join方法那個線程對象銷毀,才繼續向下執行。

              但是源碼中只有wait的方法,沒有notify的方法。因為notify這個操作是JVM通過檢測線程對象銷毀而調用的native方法,是C++實現的,在源碼中是找不到對應這個wait方法而存在的notify方法的。

              也就是說。

              利用Thread.join()方法來實現,join()方法的作用是等待調用線程執行完之后再執行任務。

              這個是必須線程A全部執行完,再去執行B.

              wait/notify

              如果是交替打印呢?

              必須使用wait()和notify()方法。

              一道面試題。

              編寫兩個線程,一個線程打印1~52,另一個線程打印字母A~Z,打印順序為12A34B56C……5152Z,要求使用線程間的通信。

              代碼如下:

              import java.util.concurrent.ExecutorService;
              import java.util.concurrent.Executors;
              
              /**
               * Created by Edison Xu on 2017/3/2.
               */
              public enum Helper {
              
               instance;
              
               private static final ExecutorService tPool = Executors.newFixedThreadPool(2);
              
               public static String[] buildNoArr(int max) {
                String[] noArr = new String[max];
                for(int i=0;i

              使用wait和notify

              public class MethodOne {
               private final ThreadToGo threadToGo = new ThreadToGo();
               public Runnable newThreadOne() {
                final String[] inputArr = Helper.buildNoArr(52);
                return new Runnable() {
              private String[] arr = inputArr;
              public void run() {
               try {
                for (int i = 0; i < arr.length; i=i+2) {
              synchronized (threadToGo) {
               while (threadToGo.value == 2)
                threadToGo.wait();
               Helper.print(arr[i], arr[i + 1]);
               threadToGo.value = 2;
               threadToGo.notify();
              }
                }
               } catch (InterruptedException e) {
                System.out.println("Oops...");
               }
              }
                };
               }
               public Runnable newThreadTwo() {
                final String[] inputArr = Helper.buildCharArr(26);
                return new Runnable() {
              private String[] arr = inputArr;
              public void run() {
               try {
                for (int i = 0; i < arr.length; i++) {
              synchronized (threadToGo) {
               while (threadToGo.value == 1)
                threadToGo.wait();
               Helper.print(arr[i]);
               threadToGo.value = 1;
               threadToGo.notify();
              }
                }
               } catch (InterruptedException e) {
                System.out.println("Oops...");
               }
              }
                };
               }
               class ThreadToGo {
                int value = 1;
               }
               public static void main(String args[]) throws InterruptedException {
                MethodOne one = new MethodOne();
                Helper.instance.run(one.newThreadOne());
                Helper.instance.run(one.newThreadTwo());
                Helper.instance.shutdown();
               }
              }
              

              注意:

              wait和notify方法必須放在同步塊里面,因為要對應同一個對象監視器。而sleep沒有

              原理詳解:

              wait方法會使執行該wait方法的線程停止,直到等到了notify的通知。細說一下,執行了wait方法的那個線程會因為wait方法而進入等待狀態,該線程也會進入阻塞隊列中。而執行了notify那個線程在執行完同步代碼之后會通知在阻塞隊列中的線程,使其進入就緒狀態。被重新喚醒的線程會試圖重新獲得臨界區的控制權,也就是對象鎖,然后繼續執行臨界區也就是同步語句塊中wait之后的代碼。

              上面這個描述,可以看出一些細節。

              1.wait方法進入了阻塞隊列,而上文講過執行notify操作的線程與執行wait的線程是擁有同一個對象監視器,也就說wait方法執行之后,立刻釋放掉鎖,這樣,另一個線程才能執行同步代碼塊,才能執行notify。

              2.notify線程會在執行完同步代碼之后通知在阻塞隊列中的線程,也就是說notify的那個線程并不是立即釋放鎖,而是在同步方法執行完,釋放鎖以后,wait方法的那個線程才會繼續執行。

              3.被重新喚醒的線程會試圖重新獲得鎖,也就說,在notify方法的線程釋放掉鎖以后,其通知的線程是不確定的,看具體是哪一個阻塞隊列中的線程獲取到對象鎖。

              這里詳細說一下,這個結果。wait使線程進入了阻塞狀態,阻塞狀態可以細分為3種:

              ● 等待阻塞:運行的線程執行wait方法,JVM會把該線程放入等待隊列中。

              ● 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池當中。

              ● 其他阻塞: 運行的線程執行了Thread.sleep或者join方法,或者發出I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止,或者超時、或者I/O處理完畢時,線程重新轉入可運行狀態。

              可運行狀態就是線程執行start時,就是可運行狀態,一旦CPU切換到這個線程就開始執行里面的run方法就進入了運行狀態。

              上面會出現這個結果,就是因為notify僅僅讓一個線程進入了可運行狀態,而另一個線程則還在阻塞中。而notifyAll則使所有的線程都從等待隊列中出來,而因為同步代碼的關系,獲得鎖的線程進入可運行態,沒有得到鎖的則進入鎖池,也是阻塞狀態,但是會因為鎖的釋放而重新進入可運行態。所以notifyAll會讓所有wait的線程都會繼續執行。

              Lock和Condition

              如何程序不使用synchronized關鍵字來保持同步,而是直接適用Lock對像來保持同步,則系統中不存在隱式的同步監視器對象,也就不能使用wait()、notify()、notifyAll()來協調線程的運行.

              當使用LOCK對象保持同步時,JAVA為我們提供了Condition類來協調線程的運行。關于Condition類,JDK文檔里進行了詳細的解釋.,再次就不啰嗦了。

              代碼如下:

              public class MethodTwo {
               private Lock lock = new ReentrantLock(true);
               private Condition condition = lock.newCondition();
               private final ThreadToGo threadToGo = new ThreadToGo();
               public Runnable newThreadOne() {
                final String[] inputArr = Helper.buildNoArr(52);
                return new Runnable() {
              private String[] arr = inputArr;
              public void run() {
               for (int i = 0; i < arr.length; i=i+2) {
                try {
              lock.lock();
              while(threadToGo.value == 2)
               condition.await();
              Helper.print(arr[i], arr[i + 1]);
              threadToGo.value = 2;
              condition.signal();
                } catch (InterruptedException e) {
              e.printStackTrace();
                } finally {
              lock.unlock();
                }
               }
              }
                };
               }
               public Runnable newThreadTwo() {
                final String[] inputArr = Helper.buildCharArr(26);
                return new Runnable() {
              private String[] arr = inputArr;
              public void run() {
               for (int i = 0; i < arr.length; i++) {
                try {
              lock.lock();
              while(threadToGo.value == 1)
               condition.await();
              Helper.print(arr[i]);
              threadToGo.value = 1;
              condition.signal();
                } catch (Exception e) {
              e.printStackTrace();
                } finally {
              lock.unlock();
                }
               }
              }
                };
               }
               class ThreadToGo {
                int value = 1;
               }
               public static void main(String args[]) throws InterruptedException {
                MethodTwo two = new MethodTwo();
                Helper.instance.run(two.newThreadOne());
                Helper.instance.run(two.newThreadTwo());
                Helper.instance.shutdown();
               }
              }
              

              輸出結果和上面是一樣的! 只不過這里 顯示的使用Lock對像來充當同步監視器,使用Condition對象來暫停指定線程,喚醒指定線程!

              共享變量(volatile、AtomicInteger)

              volatile修飾的變量值直接存在main memory里面,子線程對該變量的讀寫直接寫入main memory,而不是像其它變量一樣在local thread里面產生一份copy。volatile能保證所修飾的變量對于多個線程可見性,即只要被修改,其它線程讀到的一定是最新的值。

              代碼如下:

              public class MethodThree {
               private volatile ThreadToGo threadToGo = new ThreadToGo();
               class ThreadToGo {
                int value = 1;
               }
               public Runnable newThreadOne() {
                final String[] inputArr = Helper.buildNoArr(52);
                return new Runnable() {
              private String[] arr = inputArr;
              public void run() {
               for (int i = 0; i < arr.length; i=i+2) {
                while(threadToGo.value==2){}
                Helper.print(arr[i], arr[i + 1]);
                threadToGo.value=2;
               }
              }
                };
               }
               public Runnable newThreadTwo() {
                final String[] inputArr = Helper.buildCharArr(26);
                return new Runnable() {
              private String[] arr = inputArr;
              public void run() {
               for (int i = 0; i < arr.length; i++) {
                while(threadToGo.value==1){}
                Helper.print(arr[i]);
                threadToGo.value=1;
               }
              }
                };
               }
               public static void main(String args[]) throws InterruptedException {
                MethodThree three = new MethodThree();
                Helper.instance.run(three.newThreadOne());
                Helper.instance.run(three.newThreadTwo());
                Helper.instance.shutdown();
               }
              }
              

              管道

              管道流是JAVA中線程通訊的常用方式之一,基本流程如下:

              1)創建管道輸出流PipedOutputStream pos和管道輸入流PipedInputStream pis

              2)將pos和pis匹配,pos.connect(pis);

              3)將pos賦給信息輸入線程,pis賦給信息獲取線程,就可以實現線程間的通訊了

              代碼如下:

              import java.io.IOException;
              import java.io.PipedInputStream;
              import java.io.PipedOutputStream;
              
              public class testPipeConnection {
              
               public static void main(String[] args) {
                /**
              * 創建管道輸出流
              */
                PipedOutputStream pos = new PipedOutputStream();
                /**
              * 創建管道輸入流
              */
                PipedInputStream pis = new PipedInputStream();
                try {
              /**
               * 將管道輸入流與輸出流連接 此過程也可通過重載的構造函數來實現
               */
              pos.connect(pis);
                } catch (IOException e) {
              e.printStackTrace();
                }
                /**
              * 創建生產者線程
              */
                Producer p = new Producer(pos);
                /**
              * 創建消費者線程
              */
                Consumer1 c1 = new Consumer1(pis);
                /**
              * 啟動線程
              */
                p.start();
                c1.start();
               }
              }
              
              /**
               * 生產者線程(與一個管道輸入流相關聯)
               * 
               */
              class Producer extends Thread {
               private PipedOutputStream pos;
              
               public Producer(PipedOutputStream pos) {
                this.pos = pos;
               }
              
               public void run() {
                int i = 0;
                try {
              while(true)
              {
              this.sleep(3000);
              pos.write(i);
              i++;
              }
                } catch (Exception e) {
              e.printStackTrace();
                }
               }
              }
              
              /**
               * 消費者線程(與一個管道輸入流相關聯)
               * 
               */
              class Consumer1 extends Thread {
               private PipedInputStream pis;
              
               public Consumer1(PipedInputStream pis) {
                this.pis = pis;
               }
              
               public void run() {
                try {
              while(true)
              {
              System.out.println("consumer1:"+pis.read());
              }
                } catch (IOException e) {
              e.printStackTrace();
                }
               }
              }
              

              程序啟動后,就可以看到producer線程往consumer1線程發送數據。

              consumer1:0
              consumer1:1
              consumer1:2
              consumer1:3
              ......
              

              管道流雖然使用起來方便,但是也有一些缺點

              1)管道流只能在兩個線程之間傳遞數據

              線程consumer1和consumer2同時從pis中read數據,當線程producer往管道流中寫入一段數據后,每一個時刻只有一個線程能獲取到數據,并不是兩個線程都能獲取到producer發送來的數據,因此一個管道流只能用于兩個線程間的通訊。不僅僅是管道流,其他IO方式都是一對一傳輸。

              2)管道流只能實現單向發送,如果要兩個線程之間互通訊,則需要兩個管道流

              可以看到上面的例子中,線程producer通過管道流向線程consumer發送數據,如果線程consumer想給線程producer發送數據,則需要新建另一個管道流pos1和pis1,將pos1賦給consumer1,將pis1賦給producer,具體例子本文不再多說。

              點擊復制鏈接 與好友分享!回本站首頁
              上一篇:java排序算法學習之冒泡,選擇排序,二分查找,工具類等實現講解
              下一篇:java線程方法之join簡單總結(代碼實例)
              相關文章
              圖文推薦
              點擊排行

              關于我們 | 聯系我們 | 廣告服務 | 投資合作 | 版權申明 | 在線幫助 | 網站地圖 | 作品發布 | Vip技術培訓 | 舉報中心

              版權所有: 紅黑聯盟--致力于做實用的IT技術學習網站

              极速飞艇好假
              <dl id="opymh"></dl>

              <div id="opymh"></div>
                  <div id="opymh"><tr id="opymh"></tr></div>

                    <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

                    <em id="opymh"></em>

                    <em id="opymh"><ol id="opymh"></ol></em>

                          <dl id="opymh"></dl>

                          <div id="opymh"></div>
                              <div id="opymh"><tr id="opymh"></tr></div>

                                <em id="opymh"><ins id="opymh"><mark id="opymh"></mark></ins></em><sup id="opymh"><menu id="opymh"></menu></sup>

                                <em id="opymh"></em>

                                <em id="opymh"><ol id="opymh"></ol></em>