java高併發編程(二)——瞭解線程

java並行程序基礎

一、線程與線程的狀態

  在學習java的過程中已經接觸過很多有關線程的概念了,主要記錄一下線程的機中狀態,以及狀態之間的切換。
  線程的狀態主要分爲線程的初始化(New),可運行(Runnable),運行(Running),阻塞(Blocking),死亡(Dead)
在這裏插入圖片描述
  線程新建(new)之後線程沒有立即得到執行,等線程調用start()方法時,線程纔開始執行。當線程執行時,先處於Runnable狀態,當線程獲取到cpu資源之後進入運行狀態,如果線程在運行的過程中遇到synchronized同步塊等,就會進入到同步阻塞狀態,此時線程暫停執行,直到獲取所需要的鎖,再繼續運行。運行中的線程調用wait()方法後進入等待阻塞,此時線程期待notify()方法喚醒線程,然後等待獲取所需的鎖,然後再繼續運行。運行中的線程,調用sleep()方法或者調用io請求或者調用join()方法時,當sleep()方法結束,join()方法線程運行結束或者終止,io處理完畢,然後進入就緒狀態。這裏對join()方法理解不清晰,自己做了實踐

public class JoinTest {
	static class MyThread implements Runnable{
		private String name;
		public MyThread(String name) {
			this.name = name;
		}
		@Override
		public void run() {
			for(int i = 0;i<10;i++) {
				System.err.println(name+"-------"+i);
			}
		}
	}
	
	public static void main(String[] args) {
		Thread t1 = new Thread(new MyThread("AAA"));
		Thread t2 = new Thread(new MyThread("BBB"));
		Thread t3 = new Thread(new MyThread("CCC"));
		
		t1.start();
		t2.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t3.start();
	}
}

當不調用join()方法的時候t1,t2,t3交替執行。運行結果如下

BBB-------0
CCC-------0
AAA-------0
CCC-------1
CCC-------2
CCC-------3
CCC-------4
CCC-------5
..........

代碼中join()方法取消註釋後

		t1.start();
		t2.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t3.start();

運行結果如下

AAA-------0
AAA-------1
BBB-------0
AAA-------2
BBB-------1
AAA-------3
BBB-------2
BBB-------3
AAA-------4
AAA-------5
AAA-------6
BBB-------4
BBB-------5
BBB-------6
BBB-------7
BBB-------8
BBB-------9
AAA-------7
AAA-------8
AAA-------9
CCC-------0
CCC-------1
CCC-------2
CCC-------3
CCC-------4
CCC-------5
CCC-------6
CCC-------7
CCC-------8
CCC-------9

  這裏join()方法對已經處於運行狀態的線程並沒有影響,join()方法的作用是在t1運行結束完成後t3纔開始執行,之前t3.start()一直沒有執行,直到t1執行完成後纔開始執行。之後再去看下源碼深入理解一下。
  再記一下sleep()和wait()的區別,兩者都是讓線程暫停處於休眠的方法,但是sleep()不釋放鎖,執行sleep()的線程在休眠期間一直持有鎖,其他需要該鎖的線程無法得到鎖,而wait()方法會釋放線程佔用的鎖,其他的線程有可能獲取該鎖,並開始運行,wait()方法最典型的例子就是生產者-消費者模型

public class ProAndCon {
	static class Box{
		private int ball = 0;
		private int Max = 5;
		
		public synchronized void produce() {
			if(ball == Max) {
				try {
					wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			ball++;
			System.err.println("AAA生產了一個ball,當前的庫存爲:"+ball);
			notifyAll();
		}
		
		public synchronized void consum() {
			if(ball == 0) {
				try {
					wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			ball--;
			System.err.println("BBB消費了一個ball,當前的庫存爲:"+ball);
			notifyAll();
		}
	}
	
	static class Producer implements Runnable{
		private String name;
		private Box box;
		public Producer(String name,Box box) {
			this.name = name;
			this.box = box;
		}
		@Override
		public void run() {
			for(int i = 0;i<10;i++) {
				box.produce();
			}
		}
	}
	
	static class Consumer implements Runnable{
		private String name;
		private Box box;
		public Consumer(String name,Box box) {
			this.name = name;
			this.box = box;
		}
		@Override
		public void run() {
			for(int i = 0;i<10;i++) {
				box.consum();;
			}
		}
		
	}
	
	public static void main(String[] args) {
		Box box = new Box();
		Thread t1 = new Thread(new Producer("AAA",box));
		Thread t2 = new Thread(new Consumer("BBB",box));
		
		t1.start();
		t2.start();
	}
}

輸出結果爲:

AAA生產了一個ball,當前的庫存爲:1
BBB消費了一個ball,當前的庫存爲:0
AAA生產了一個ball,當前的庫存爲:1
AAA生產了一個ball,當前的庫存爲:2
AAA生產了一個ball,當前的庫存爲:3
AAA生產了一個ball,當前的庫存爲:4
AAA生產了一個ball,當前的庫存爲:5
BBB消費了一個ball,當前的庫存爲:4
BBB消費了一個ball,當前的庫存爲:3
BBB消費了一個ball,當前的庫存爲:2
BBB消費了一個ball,當前的庫存爲:1
BBB消費了一個ball,當前的庫存爲:0
AAA生產了一個ball,當前的庫存爲:1
AAA生產了一個ball,當前的庫存爲:2
AAA生產了一個ball,當前的庫存爲:3
AAA生產了一個ball,當前的庫存爲:4
BBB消費了一個ball,當前的庫存爲:3
BBB消費了一個ball,當前的庫存爲:2
BBB消費了一個ball,當前的庫存爲:1
BBB消費了一個ball,當前的庫存爲:0

二、線程的基本操作

(1)新建線程

   新建一個線程只需要new 一個Thread的實例就行了

		Thread t = new Thread()
		t.start();

   start()方法創建一個線程並讓線程進入就緒狀態,start()內部調用了run()方法,在這裏,如果直接寫t.run()也是能通過編譯正常執行的,但是卻只是執行了一個相當於執行了一個普通類的run方法,並不會創建一個線程。
   線程的創建有三種方法,繼承Thread類,實現Runnable接口,實現Callable接口,前兩者需要重寫run()方法,後者需要重寫call()方法。

1.public class MyThread extends Thread{}
2.public class MyThread implements Runnable{}
3.public class MyThread implements Callable{}
(2)終止線程

   一般線程執行完畢之後就會結束,無須手動關閉。然而有些線程可能執行着大量循環用於提供某種服務。Thread類提供了一個stop()方法,但是該方法在eclipse等編寫時會標註該方法已被廢棄。原因是stop()方法會強行終止一個線程,並釋放鎖,這樣的話可能在寫數據寫到一半是強行退出,由另一線程獲取鎖並向同一個對象寫入其他數據,導致數據損壞。終止線程的實現方法可以在類中增加一個stopMe()方法,在run()方法中加上while判斷。

boolean stop = false;
public void stopMe(){
	stop = ture;
}
public void run(){
	while(stop = ture){
		break;
	}
.....
....
...
}
(3)線程中斷

   線程中斷是一種重要測線程協作機制,線程中斷之後不會立即退出,而是給線程一個退出的通知,至於線程接受到通知之後怎麼操作,由線程自己決定。與線程中斷有關的有三個方法

public void Thread.interrupt();							//中斷線程
public boolean	 Thread.isInterrupted();					//判斷線程是否中斷						
public static boolean Thread.interrupted();				//判斷是否中斷,並清除當前中斷狀態

   記錄一下這三個方法的具體使用

public class ThreadInterrupt {
	static class MyThread implements Runnable{
		@Override
		public synchronized void run() {
			while(true) {
				if(Thread.currentThread().isInterrupted()) {
					System.err.println("線程被中斷了");
					System.err.println(Thread.interrupted());   //interrupted()重置了中斷狀態
					System.err.println(Thread.interrupted()); 
					if(Thread.currentThread().isInterrupted() == false) {
						System.err.println("線程未被中斷");
						break;
					}
				}
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					System.err.println("睡眠時執行了線程中斷");
					//設置中斷狀態
					Thread.currentThread().interrupt();
				}
				
				for(int i = 0;i<5;i++) {
					System.err.println(i);
				}
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread(new MyThread());
		t.start();
		Thread.sleep(2000);
		t.interrupt();
	}
}

輸出結果爲

睡眠時執行了線程中斷
0
1
2
3
4
線程被中斷了
true
flase
線程未被中斷

  線程運行後直接sleep兩秒鐘,如果在這期間執行了interrupt()來中斷線程,那麼就會拋一個InterruptException中斷異常,因此要進行後續的處理再次提醒線程是時候該中斷了;但是這只是一個通知,接下來直接是for循環, 並沒有指定收到中斷通知之後的操作,因此打印了5個數字,然後再次進入while循環,if語句判斷是否有中斷線程的通知,isInterrupted()返回true執行if內代碼,並輸出現場被中斷了,此時線程實際上還是沒有中斷運行。然後輸出了interrupted(),返回結果爲ture,說明線程收到中斷通知要準備中斷了,但是interrupted()方法重置了,再次執行相同的語句時,返回的就是false了,接下來的if語句判斷interrupted()返回爲false時打印語句,並人爲控制的跳出了循環,注意前面的interrupted()已經重置了中斷狀態,此時的線程並未收到中斷的通知而做出的操作。

(4)等待(wait)和通知(notify)

  爲了支持多線程之間的合作,JDK提供了兩個重要的接口,線程等待wait()方法和線程喚醒notify()方法;這兩個方法是包含在Object類中的,因此所有的對象都能使用它們。當線程A調用了wait()方法之後,線程A就會停止繼續執行,轉爲等待狀態,進入等待隊列,一直到其他線程執行notify()方法或者notifyAll()方法後線程A被喚醒。需要注意的是,如果等待隊列中有很多處理等待的進程的話,notify()方法會隨機選取一個線程喚醒,並不是按照先來後到的順序。wait()方法雖然是每個對象都擁有的方法,但是卻不是可以隨時隨地就調用的,wait()方法一定要在synchronized代碼塊或方法中才可以使用,而且,wait()和notify()都需要一個目標對象的監視器。線程A在執行wait()方法之前先獲得object對象的監視器,wait()正確執行之後釋放該監視器;線程B在調用notify()之前,也必須獲得這個object監視器,然後再motify()方法正確運行之後也釋放該監視器。在線程A被喚醒之後,做的第一件事是重新獲取object監視器,如果暫時沒有獲得這個監視器就必須要等到獲得這個監視器才能繼續往下運行。
  有關wait()方法和notify()方法最好的理解就是上面的生產者-消費者模型。這列不再重複寫一遍了。

(5)掛起(suspend)和繼續執行(resume)線程

  線程掛起和繼續執行就是字面表面的意思,當一個線程被掛起後就暫停了,必須等到resume()操作之後才能繼續運行,但是現在這兩個方法已經被廢棄了,因爲在掛起的期間並不會釋放鎖,而且resume()意外的在suspend()方法之前被執行了的話,它所佔有的鎖可能不會釋放,導致系統出問題。

(6)等待線程結束(join)和謙讓(yield)

  join在上面實踐中已經差不多理解清晰了。主要記一下yield()方法,該方法一旦進行,線程會自動讓出cpu資源,但是讓出資源之後,該線程還是會參與cpu資源的競爭,但是能否被分配到就不一定了。該方法可以用於對一些不怎麼重要的線程,在佔用cpu資源時,完成了比較重要的工作就把cpu資源讓給那些優先級高或者比較重要的線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章