線程sleep()、wait()、yield()、join()方法 解析

 java多線程機制

JAVA多線程機制有兩種實現方式:

第一種:  繼承Thread類, 實現run()方法.

第二種: 實現Runnable接口.

      實例代碼:

      第一種:

                 public class OntThread extends Thread {
       public static void main(String[] args) {

        OntThread tsub = new OntThread();
          tsub.start();

        try {
             Thread.sleep(1000);
             System.out.println("main");
          } catch (InterruptedException e) {
             e.printStackTrace();
          }

      }

      public void run() {
          System.out.println("sub");
       }

    }

  第二種:

    public class ThreadRunnable implements Runnable {

       public void run() {
          System.out.println("sub");
       }

    }

    public class TwoThread {
       public static void main(String[] args) {
          ThreadRunnable sub = new ThreadRunnable();
           Thread tsub = new Thread(sub);
           tsub.start();
          try {
             Thread.sleep(1000);
             System.out.println("main");
          } catch (InterruptedException e) {
             e.printStackTrace();
          }
       }

    }

兩種方式的運行結果一樣: 即:   sub   main

注:這裏的Thread.sleep(1000);是讓主線程停止1000納秒. 這時子線程正在工作中.

 

二  Thread 類的常用函數及功能:

 

1  sleep()   使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會執行, 同時sleep函數不會釋放鎖資源.

              sleep可使優先級低的線程得到執行的機會,當然也可以讓同優先級和高優先級的線程有執行的機會

 

2  yield()  只是使當前線程重新回到可執行狀態,所以執行yield()線程有可能在進入到可執行狀態後馬上又被執行. 只能使同優先級的線程有執行的機會。同樣, yield()也不會釋放鎖資源.

 

      sleep和yield的區別在於, sleep可以使優先級低的線程得到執行的機會,  而yield只能使同優先級的線程有執行的機會.

代碼示例:

            public class ThreadYieldOne implements Runnable {
     public String name;
     public void run() {
        for (int i = 0; i < 10; i++) {
           Thread.yield();    // (2)  Thread.sleep(100);
           System.out.println(name + " :" + i);
        }
     }
  }

  public static void main(String[] args) {
      ThreadYieldOne one = new ThreadYieldOne();
      ThreadYieldOne two = new ThreadYieldOne();
      one.name = "one";
      two.name = "two";
      Thread t1 = new Thread(one);
      Thread t2 = new Thread(two);
      t1.setPriority(Thread.MAX_PRIORITY);
      t2.setPriority(Thread.MIN_PRIORITY);   // (1) t2.setPriority(Thread.MAX_PRIORITY);   
      t1.start();
      t2.start();
   }

  代碼執行結果:  
one :0 one :1 one :2 one :3 one :4 one :5 one :6 one :7 one :8 one :9
two :0 two :1 two :2 two :3 two :4 two :5 two :6 two :7 two :8 two :9

        注: (1) 處 如果放開註釋掉t2.setPriority(Thread.MIN_PRIORITY);   , 則執行結果將會改變: 如下:  one和two 交替打印.

one :0 one :1 one :2 one :two :0 two :1 two :2 two :3 one :4 one :5 one :3 two :4 two :5 two :6 two :6 one :7 one :8 one :9
7 two :8 two :9

   (2)處 如果放開並洲釋掉Thread.yield(); , 則掃行結果也是one 和two 交替打印, 並且, 它不受(1)處的影響.

 

 

3   stop()  些方法可以中止一個正在運行的線程, 但這樣的方法並不安全.   強列建議不要使用此函數來中斷線程.

           注:  stop方法是立即停止當前線程,  這樣停止的後果是導致stop後的語句無法執行, 有可能資源未釋放. 列或者在同步塊中調用此方法會導致同步數據會不完整. 所以需禁用此方法.  由於stop方法的特釋性, 將不給出示範代碼.

 

 

4   interrupt()  一個線程意味着在該線程完成任務之前停止其正在進行的一切,有效地中止其當前的操作。線程是死亡、還是等待新的任務或是繼續運行至下一步,就取決於這個程序.

                  Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一箇中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態,

                 兩個解釋爲什麼會相沖突呢, 一個解釋是可以中斷一個線程. 一個解釋說不會中斷一個正在運行的線程.  仔細看會發現其中的奧秒. interrupt方法不會中斷一個正在運行的線程.就是指線程如果正在運行的過程中, 去調用此方法是沒有任何反應的. 爲什麼呢, 因爲這個方法只是提供給 被阻塞的線程, 即當線程調用了.Object.wait, Thread.join, Thread.sleep三種方法之一的時候, 再調用interrupt方法, 纔可以中斷剛纔的阻塞而繼續去執行線程.

             代碼說明一切.

            情況1.  不會中斷線程.

                            public class guoInterrupt extends Thread {
           public boolean stop = false;
         public static void main(String[] args) throws InterruptedException {
            guoInterrupt t1 = new guoInterrupt();
            System.out.println("app is starting");
            t1.start();
            Thread.sleep(3000);
            System.out.println("Interrupting t1....");
            t1.interrupt();
            Thread.sleep(3000);
            System.out.println("app is end");
            t1.stop = true;
            System.exit(0);
         }
         public void run() {
            while(!this.stop) {
               System.out.println("t1 running...........");
             l  ong time = System.currentTimeMillis();
               while ( (System.currentTimeMillis()-time < 1000) && (!stop) ) {};
            }
            System.out.println("t1 is end");
         }
      }

      執行結果:

app is starting
t1 running...........
t1 running...........
t1 running...........
t1 running...........
Interrupting t1....
t1 running...........
t1 running...........
t1 running...........
app is end
t1 is end

 

結果說明. 當調用了interrupt時, t1線程仍在執行, 並沒有中斷線程. 直到main掃行結束後, 改t1的stop值時 t1線程才執行結束.

 

    情況2:  interrupt中斷線程.

                             public class guoInterrupt extends Thread {
           public boolean stop = false;
           public static void main(String[] args) throws InterruptedException {
              guoInterrupt t1 = new guoInterrupt();
              System.out.println("app is starting");
              t1.start();
              Thread.sleep(3000);
              System.out.println("Interrupting t1....");
              t1.stop = true;
              t1.interrupt();
              Thread.sleep(3000);
              System.out.println("app is end");
              System.exit(0);
           }
           public void run() {
              while(!this.stop) {
                 System.out.println("t1 running...........");
                 try {
                    Thread.sleep(1000);
                 } catch (InterruptedException e) {
                    System.out.println("t1 is Interrupting......");
                 }
              }
              System.out.println("t1 is end");
           }
        }  

      執行結果:

app is starting
t1 running...........
t1 running...........
t1 running...........
Interrupting t1....
t1 is Interrupting......
t1 is end
app is end

 

結果說明: 當執行了 t1.interrupt();方法時, 線程立即產生了一個.InterruptedException 異常.

 

 

 

5   join()  當join(0)時等待一個線程直到它死亡,  當join(1000)時等待一個線程1000納秒,後回到主線程繼續執行.

         代碼示例:

    public static void main(String[] args) {
        ThreadJoin t = new ThreadJoin();
        try {
            t.start();
            Thread.sleep(1000);
            System.out.println("main join start");
            t.join(0);  // (1)
            System.out.println("main join end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }             

 public class ThreadJoin extends Thread {
    public void run() {
            System.out.println("join start");
            try {
                Thread.sleep(9000);
                for (int i = 0; i < 5; i++) {
                    System.out.println("sub thread:" + i);
                }
            } catch (InterruptedException e) {
            }
            System.out.println("join end");
    }
}

 說明: (1) 爲t.join(0)時 等待t線程執行完之後回到main函數的主線程繼續處理.

執行結果

join start
main join start
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end
main join end

 

             (1) 處改爲t.join(1000)時, main函數的主線程等待t經程1000納秒後繼續執行

   執行結果:

join start
main join start
main join end
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end

注:  join函數爲線程安全函數, 即同步函數. 也就是說上面的例子, 當ThreadJoin類的run用synchronized鎖住時, t.join方法將得不到鎖資源而等待更長的時間.

代碼示例:

public class guoJoin {
    public static void main(String[] args) {
        ThreadJoin t = new ThreadJoin();
        try {
            t.start();
            Thread.sleep(1000);
            System.out.println("main join start");
            t.join(1000);
            System.out.println("main join end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadJoin extends Thread {
    public void run() {
        synchronized (this) {
            System.out.println("join start");
            try {
                Thread.sleep(9000);
                for (int i = 0; i < 5; i++) {
                    System.out.println("sub thread:" + i);
                }
            } catch (InterruptedException e) {
            }
            System.out.println("join end");
        }
    }
}

執行結果

join start
main join start
sub thread:0
sub thread:1
sub thread:2
sub thread:3
sub thread:4
join end
main join end

結果說明: 當main函數中調用t.join();時, 由於 t.run()方法對t線程做了同步處理, 即得到了鎖資源, 而此時t.join()方法調用時只能等待t.run()方法執行完成之後, 釋放了鎖資源之後. t.join函數纔可以繼續等待. 即main實際等待了 9000 + 1000納秒.

 

 

 

6   suspend(), resume() 這兩個是JDK的過期方法. suspend()函數,可使線程進入停滯狀態.  通過suspend()使線程進入停滯狀態後,除非收到resume()消息,否則該線程不會變回可執行狀態

示範代碼

public class guoSuspend {
    public static void main(String[] args) {
        ThreadSuspend t = new ThreadSuspend();
        t.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
        }
        System.out.println("main suspend start");
        t.suspend();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        t.resume();
        System.out.println("main resume end ");
    }
}

public class ThreadSuspend extends Thread {
    public void run() {
        for (int i = 0; i < 6; i++) {
            System.out.println("ThreadSuspend:" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
}

執行結果:

ThreadSuspend:0
main suspend start
main resume end 
ThreadSuspend:1
ThreadSuspend:2
ThreadSuspend:3
ThreadSuspend:4
ThreadSuspend:5

 

三  多線程的同步

 

synchronized, wait, notify

線程的同步需要依靠上面兩個函數和一個同步塊實現.

     同步, 即兩個線程爲了一件事情協同合作執行. 例如, 緩衝區的讀寫操作. 在實際應用中, 例如:/

           有一個緩衝區, 由兩個線程進行操作, 一個寫線程, 一個讀線程. 寫線程完成對緩衝區的寫入, 讀線程完成對線程的讀取, 只有當緩衝區寫入數據時, 讀線程纔可以讀取, 同樣. 只有當緣衝區的數據讀出時,  寫線程纔可以向緣衝區寫入數據.

代碼示例:

public class ThreadWrite extends Thread {
    public StringBuffer buffer;
    public ThreadWrite(StringBuffer buffer) {
        this.buffer = buffer;
    }
    public void run() {
        synchronized (this.buffer) {
            for (int i = 0; i < 5; i++) {
                if (!"".equals(this.buffer.toString())) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } 
                System.out.println("Write start");
                this.buffer.append("123");
                this.buffer.notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("Write end");
            }
        }
    }
}

 

public class ThreadRead extends Thread {
    public StringBuffer buffer;
    public ThreadRead(StringBuffer buffer) {
        this.buffer = buffer;
    }
    public void run() {
        synchronized (buffer) {
            for (int i = 0; i < 5; i++) {
                if ("".equals(this.buffer.toString().trim())) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("read start");
                System.out.println(this.buffer.toString());
                buffer.delete(0, buffer.toString().length());
                buffer.notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                }
                System.out.println("read end");
            }
        }
    }
}

public class GuoSynchronized {
    public static void main(String[] args) {
        StringBuffer bufer = new StringBuffer("");
        ThreadWrite write = new ThreadWrite(bufer);
        ThreadRead read = new ThreadRead(bufer);
        read.start();
        write.start();
    }
}

執行結果:

Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end
Write start
Write end
read start
123
read end

結果說明: 這個結果不管執行多少次, 都會是相同的結果, 即多線程同步的特點.

注: synchronized  關鍵字的同步, 在兩個線程之間必須是同一個對象, 即 對同一個對象進行同步訪問.

 

四  死鎖

 

爲什麼會產生死鎖? 在多線程環境裏, 產生死鎖的原因是由於幾個線程同時共用一個同步資源. 這是產生死鎖的前提,

產生死鎖的原因, 所有線程都在等待共享資源的釋放.

例如:

線程1寫緩衝區時, 緩衝區已滿 , 線程1等待. 

線程2讀緩衝區, 當緩衝區讀完時, 喚醒共享資源鎖上的1個線程, 並且線程2再去讀取緩衝區時, 由於緩衝區被讀完, 線程2等持.

剛剛線程2喚醒了一個共享資源鎖上的1個線程, 而共享資源鎖上的線程可能有好幾個, 有可能是讀線程, 有可能是寫線程. 假如此時喚醒的正好是讀線程3, 那麼線程3去讀取緩衝區, 這時的緩衝區已經線程2讀完了, 線程3需要等持緩衝區被寫入數據時纔可以讀取緩衝區, 即此時線程3也被阻塞了. 即等待.

此時, 線程1等待,    線程2由於讀完了, 也等待, 線程3的情況和2一樣, 也等待. 其它共享資源上的線程也都在等待, 死鎖即發生了.

 

看代碼示例:

public class GuoSynchronized {
    public static void main(String[] args) {
        StringBuffer bufer = new StringBuffer("");
        ThreadWrite write1 = new ThreadWrite(bufer,"w1");
        ThreadWrite write2 = new ThreadWrite(bufer,"w2");
        ThreadWrite write3 = new ThreadWrite(bufer,"w3");
        ThreadRead read1 = new ThreadRead(bufer,"r1");
        ThreadRead read2 = new ThreadRead(bufer,"r2");
        ThreadRead read3 = new ThreadRead(bufer,"r3");
        read1.start();
        read2.start();
        read3.start();
        write1.start();
        write2.start();
        write3.start();
    }
}

public class ThreadRead extends Thread {
    public StringBuffer buffer;
    public ThreadRead(StringBuffer buffer, String name) {
        super(name);
        this.buffer = buffer;
    }
    public void run() {
        synchronized (buffer) {
            for (int i = 0; i < 5; i++) {
                while ("".equals(this.buffer.toString().trim())) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("read " + Thread.currentThread().getName() + " start");
                System.out.println(this.buffer.toString());
                buffer.delete(0, buffer.toString().length());
                buffer.notify();
                System.out.println("read " + Thread.currentThread().getName() + " end");
            }
        }
    }
}

public class ThreadWrite extends Thread {
    public StringBuffer buffer;
    public ThreadWrite(StringBuffer buffer, String name) {
        super(name);
        this.buffer = buffer;
    }
    public void run() {
        synchronized (this.buffer) {
            for (int i = 0; i < 5; i++) {
                while (!"".equals(this.buffer.toString())) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } 
                System.out.println("Write " + Thread.currentThread().getName() + " start");
                this.buffer.append("123");
                System.out.println("123");
                this.buffer.notify();
                System.out.println("Write " + Thread.currentThread().getName() + " end");
            }
        }
    }
}

 

執行結果很有可能被死鎖住了.

 要改變這一狀況, 可將, 兩個線程類的this.buffer.notify();函數改爲this.buffer.notifyAll();即可解除這種死鎖.

 

上面由於存在代碼示例,顯得有些冗長,下面簡單總結下:

 

     1.sleep()方法
  在指定時間內讓當前正在執行的線程暫停執行,但不會釋放“鎖標誌”。不推薦使用。
  sleep()使當前線程進入阻塞狀態,在指定時間內不會執行。
  2.wait()方法
  在其他線程調用對象的notify或notifyAll方法前,導致當前線程等待。線程會釋放掉它所佔有的“鎖標誌”,從而使別的線程有機會搶佔該鎖。
  當前線程必須擁有當前對象鎖。如果當前線程不是此鎖的擁有者,會拋出IllegalMonitorStateException異常。
  喚醒當前對象鎖的等待線程使用notify或notifyAll方法,也必須擁有相同的對象鎖,否則也會拋出IllegalMonitorStateException異常。
  waite()和notify()必須在synchronized函數或synchronized block中進行調用。如果在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯通       過,但在運行時會發生IllegalMonitorStateException的異常。
  3.yield方法
  暫停當前正在執行的線程對象。
  yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行。
  yield()只能使同優先級或更高優先級的線程有執行的機會。
  4.join方法
  等待該線程終止。
  等待調用join方法的線程結束,再繼續執行。如:t.join();//主要用於等待t線程運行結束,若無此句,main則會執行完畢,導致結果不可預測。

比較重要的幾點需要注意:
1 sleep和yield的區別在於, sleep可以使優先級低的線程得到執行的機會,  而yield只能使同優先級的線程有執行的機會.
2 interrupt方法不會中斷一個正在運行的線程.就是指線程如果正在運行的過程中, 去調用此方法是沒有任何反應的. 爲什麼呢, 因爲這個方法只是提供給 被阻塞的線程, 即當線程調用了.Object.wait, Thread.join, Thread.sleep三種方法之一的時候, 再調用interrupt方法, 纔可以中斷剛纔的阻塞而繼續去執行線程.
3 join()  當join(0)時等待一個線程執行直到它死亡返回主線程,  當join(1000)時主線程等待一個線程1000納秒,後回到主線程繼續執行.
4 waite()和notify()必須在synchronized函數或synchronized block中進行調用。如果在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯通過,但在運行時會發生IllegalMonitorStateException的異常。
發佈了39 篇原創文章 · 獲贊 3 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章