Android開發手記(27) Java多線程的操作

來源:互聯網
上載者:User

標籤:

      Java中常用的有關線程的操作有,判斷線程是否啟動、線程強制執行、線程休眠、線程中斷、線程讓步、線程同步等。下面就一一舉例。

      首先,我們建立一個MyThread類實現Runnable介面。基於此介面進行線程的相關操作。

class MyThread implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + " 運行 " + i);}}}

1、判斷線程是否啟動

      判斷線程是否啟動很簡單,通過Thread.isAlive()方法即可。

public class Test {public static void main(String[] args) {MyThread mt = new MyThread();Thread t = new Thread(mt);System.out.println("線程" + (t.isAlive() ? "已" : "未") + "啟動");t.start();System.out.println("線程" + (t.isAlive() ? "已" : "未") + "啟動");}}

      輸出入下:

線程未啟動線程已啟動Thread-0 運行 0Thread-0 運行 1Thread-0 運行 2

2、線程強制執行

      線程強制執行可以通過Thread.join()實現。我們首先來看一個沒有強制執行的例子

public class Test {public static void main(String[] args) {MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();Thread t1 = new Thread(mt1);Thread t2 = new Thread(mt2);try {t1.start();// t1.join();t2.start();// t2.join();} catch (Exception e) {e.printStackTrace();}}}

      程式輸入如下:

Thread-0 運行 0Thread-1 運行 0Thread-0 運行 1Thread-1 運行 1Thread-0 運行 2Thread-1 運行 2

      我們發現,兩個線程是並行啟動並執行,因此,執行的結果為兩個線程分別執行取得的輸出然後在按照時間插入到輸出資料流中。我們將join注釋掉之後,發現程式的執行結果變為:

Thread-0 運行 0Thread-0 運行 1Thread-0 運行 2Thread-1 運行 0Thread-1 運行 1Thread-1 運行 2

      可以看出,執行的結果是線程1開始執行,等線程1執行完畢之後線程2才開始執行。

3、線程休眠

      線程休眠通過Thread.sleep()實現。這是一個靜態方法,所以需要通過Thread類來調用,而不是通過執行個體化之後的變數調用。另外,調用sleep()可能拋出InterruptedException異常,我們我們需要將其放在try-catch塊中來捕獲此異常。

public class Test {public static void main(String[] args) {MyThread mt = new MyThread();Thread t = new Thread(mt);long stime = System.currentTimeMillis();t.start();System.out.println(System.currentTimeMillis() - stime);try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}System.out.println(System.currentTimeMillis() - stime);}}

      程式執行結果為:

0Thread-0 運行 0Thread-0 運行 1Thread-0 運行 21000

      使用System.currentTimeMiles()來擷取程式啟動並執行時間。我們發現:從開始執行到結束,花了1秒鐘的時間,這個時間是main Thread休眠的時間。

4、線程中斷

      上一小節提到,線程在sleep過程中可以被打斷並拋出InterruptedException異常,這個線程中斷行為可以通過Thread.interrupt()實現。首先我們將時間統計移動到MyThread內部,然後在MyThread內每輸出一次運行情況進行一次休眠操作。

class MyThread implements Runnable {public void run() {long stime = System.currentTimeMillis();System.out.println(System.currentTimeMillis() - stime);for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + " 運行 " + i);try {Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();}}System.out.println(System.currentTimeMillis() - stime);}}public class Test {public static void main(String[] args) {MyThread mt = new MyThread();Thread t = new Thread(mt);t.start();t.interrupt();}}

      程式輸出為:

0Thread-0 運行 0java.lang.InterruptedException: sleep interruptedThread-0 運行 1at java.lang.Thread.sleep(Native Method)at MyThread.run(Test.java:9)at java.lang.Thread.run(Thread.java:745)Thread-0 運行 22002

      程式從開始運行到結束,花了2秒左右的時間,但是明明在MyThread內休眠了3次,每次一秒,應該運行3秒左右。這是因為線上程第一次休眠的時候,在main線程內通過Thread.interrupt對第一次休眠進行了打斷,所以這次休眠時間幾乎為0,剩餘的兩次休眠沒有被打斷,所以總共執行時間為2秒左右。

5、線程讓步

      有時候,線程在執行過程中,需要等待其餘線程的執行結果,才能繼續自身的運算,這時候就需要進行線程讓步。線程的讓步可以通過Thread.yield來實現。

class MyThread implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + " 運行 " + i);Thread.yield();}}}public class Test {public static void main(String[] args) {MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();Thread t1 = new Thread(mt1);Thread t2 = new Thread(mt2);t1.start();t2.start();}}

      輸出結果為:

Thread-0 運行 0Thread-1 運行 0Thread-0 運行 1Thread-1 運行 1Thread-0 運行 2Thread-1 運行 2

6、線程同步

      Java中線程同步可以通過同步方法和同步代碼塊來完成:

(1)同步方法

      我們首先來觀察一個程式的執行結果

class MyThread implements Runnable {private int Tic = 5;@Overridepublic void run() {for (int i = 0; i < 3; i++)if (Tic > 0)System.out.println(Thread.currentThread().getName() + " Ticket:" + Tic--);}}public class Test {public static void main(String[] args) {MyThread mt = new MyThread();for (int i = 0; i < 3; i++) {new Thread(mt).start();}}}

      執行結果為:

Thread-1 Ticket:5Thread-2 Ticket:3Thread-0 Ticket:4Thread-2 Ticket:1Thread-1 Ticket:2

       我們發現,其結果是3個線程不分先後並存執行,當線程0啟動並執行時候,線程2依然可以訪問到方法體內部,反之依然。有時候(比如賣票系統)我們需要在一個線程啟動並執行時候,另一個線程無法進入到正在啟動並執行方法體內部,這時候我們就需要進行線程的同步與互斥操作。通過在方法前面加入synchronized可以指定一個方法為同步方法。

class MyThread implements Runnable {private int Tic = 5;@Overridepublic synchronized void run() {for (int i = 0; i < 3; i++)if (Tic > 0)System.out.println(Thread.currentThread().getName() + " Ticket:" + Tic--);}}

      此時程式的運行結果為:

Thread-0 Ticket:5Thread-0 Ticket:4Thread-0 Ticket:3Thread-2 Ticket:2Thread-2 Ticket:1

      可以看出,在增加了同步之後,票是一張一張被賣出去的,這樣就可以有效避免重票的情況。

(2)同步代碼塊

      我們將上述代碼改為如下。依然可以實現相同的功能

class MyThread implements Runnable {private int Tic = 5;@Overridepublic void run() {synchronized (this) {for (int i = 0; i < 3; i++)if (Tic > 0)System.out.println(Thread.currentThread().getName() + " Ticket:" + Tic--);}}}

(3)注意

1)當一個線程正在訪問一個對象的synchronized方法,那麼其他線程不能訪問該對象的其他synchronized方法。這個原因很簡單,因為一個對象只有一把鎖,當一個線程擷取了該對象的鎖之後,其他線程無法擷取該對象的鎖,所以無法訪問該對象的其他synchronized方法。

2)當一個線程正在訪問一個對象的synchronized方法,那麼其他線程能訪問該對象的非synchronized方法。這個原因很簡單,訪問非synchronized方法不需要獲得該對象的鎖,假如一個方法沒用synchronized關鍵字修飾,說明它不會使用到臨界資源,那麼其他線程是可以訪問這個方法的,

3)如果一個線程A需要訪問對象object1的synchronized方法fun1,另外一個線程B需要訪問對象object2的synchronized方法fun1,即使object1和object2是同一類型),也不會產生安全執行緒問題,因為他們訪問的是不同的對象,所以不存在互斥問題。

6、生產者消費者問題

class BreadContainer {public static final int maxNum = 300;private int num;public BreadContainer() {}public BreadContainer(int num) {this.num = num;}public synchronized void produceBread(int pdcNum, String producer) {while(num + pdcNum > maxNum){System.out.println(producer + " 要生產 " + pdcNum +" 個,資源充足無需生產,轉入等待。");try{this.wait();} catch (Exception e) {e.printStackTrace();}}num += pdcNum;System.out.println(producer + " 生產了 " + pdcNum + " 個,現有 " + num + " 個");this.notifyAll();}public synchronized void consumerBread(int csmNum, String consumer) {while (csmNum > num) {System.out.println(consumer + " 要消費 " + csmNum + " 個,資源不足無法消費,轉入等待。");try {this.wait();} catch (Exception e) {e.printStackTrace();}}num -= csmNum;System.out.println(csmNum + " 消費了 " + csmNum + " 個,現有 " + num + " 個");this.notifyAll();}}class Producer extends Thread {private int pdcNum;private BreadContainer bc;public Producer() {}public Producer(int pdcNum, BreadContainer bc, String producer) {this.pdcNum = pdcNum;this.bc = bc;this.setName(producer);}public void run() {bc.produceBread(pdcNum, this.getName());}}class Consumer extends Thread {private int csmNum;private BreadContainer bc;public Consumer() {}public Consumer(int csmNum, BreadContainer bc, String consumer) {this.csmNum = csmNum;this.bc = bc;this.setName(consumer);}public void run() {bc.consumerBread(csmNum, this.getName());}}public class Test {public static void main(String[] args) {BreadContainer bc = new BreadContainer(50);Producer p1 = new Producer(50, bc, "p1");Producer p2 = new Producer(200, bc, "p2");Producer p3 = new Producer(290, bc, "p3");Consumer c1 = new Consumer(70, bc, "c1");Consumer c2 = new Consumer(80, bc, "c2");c1.start();c2.start();p1.start();p2.start();p3.start();}}

      輸出結果為:

c1 要消費 70 個,資源不足無法消費,轉入等待。p3 要生產 290 個,資源充足無需生產,轉入等待。p2 生產了 200 個,現有 250 個p1 生產了 50 個,現有 300 個80 消費了 80 個,現有 220 個p3 要生產 290 個,資源充足無需生產,轉入等待。70 消費了 70 個,現有 150 個p3 要生產 290 個,資源充足無需生產,轉入等待。

Android開發手記(27) Java多線程的操作

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.