Java多線程知識點整理(線程間通訊)

一.做用java

    使線程間通訊後,系統之間的交互性會更強大,在大大提升CPU利用率的同時還會使程序員對個線程任務在處理的過程進行有效的把控與監督。程序員

二.等待/通知機制面試

    對於傳統的,咱們能夠不使用wait/notify機制,採用while不斷的輪詢。可是不聽地經過while語句輪詢機制來檢測某一個條件,這樣會浪費CPU資源。redis

代碼:緩存

public class test2 implements Runnable{
	public String uname;
	
	public void setUname(String uname) {
		this.uname = uname;
	}


	@Override
	public void run() {
		
		while(true){
			if(uname.equals("")){
				System.out.println("退出");
				try {
					throw new InterruptedException();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		
	}

}

2.一、什麼是等待/通知機制dom

    好比在就餐時,廚師和服務業之間的交互。廚師通知服務員才作好了,服務員等待廚師菜作好。ide

2.二、等待/通知機制的實現this

    方法wait()的做用是使當前執行代碼的線程進行等待,wait()方法是Object類的方法,該方法用來將當線程置入「預執行隊列」中,而且在wait()所在的代碼行處中止執行,直到接到通知或被中斷爲止。在調用以前,線程必須得到該對象的對象級別鎖,即只能在同步方法或者同步快中調用wait()方法。在執行wait()方法後,當前線程釋放鎖。在wait()返回前,線程與其餘線程競爭從新得到鎖。spa

    插一句:面試常常會問,wait()方法和sleep()方法之間的區別。.net

    補充答案: a.sleep 是線程類(Thread)的方法,wait 是Object 類的方法。

                     b.最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其餘線程可使用同步控制塊或者方法。

                     c.wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在任何地方使用(使用範圍)。

                     d.sleep必須捕獲異常,而wait,notify和notifyAll不須要捕獲異常。

    方法notify()也要在同步方法或者同步代碼塊中調用,即在調用前,線程也必須得到該對象的對象級別鎖。若是沒有得到鎖,拋出異常。若是有多個線程等待,則由線程規劃器隨機挑選出其中一個呈wait狀態的線程,對其發出通知notify,並使它等待獲取該對象的對象鎖。注意:在執行notify()方法後,當前線程不會立刻釋放該對象鎖,呈wait狀態的線程也並不能立刻獲取該對象鎖,要等到執行notify()方法的線程將程序執行完,也就是退出synchronized代碼塊後,當前線程纔會釋放鎖,而呈wait狀態所在的線程才能夠獲取該對象鎖。注意:notify()一次只隨機通知一個線程進行喚醒。爲了喚醒所有線程可使用notifyAll()方法。

爲何wait(),notify()方法要和synchronized一塊兒使用?

由於wait()方法是通知當前線程等待並釋放對象鎖,notify()方法是通知等待此對象鎖的線程從新得到對象鎖,然而,若是沒有得到對象鎖,wait方法和notify方法都是沒有意義的,即必須先得到對象鎖,才能對對象鎖進行操做,因而,才必須把notify和wait方法寫到synchronized方法或是synchronized代碼塊中了。若是沒有獲取到鎖,會拋出IllegalMonitorStateException,它是RuntimeException的一個子類。

總結:wait使線程中止運行,而notify使中止的線程繼續運行。

代碼:等待代碼

public class test2 implements Runnable{

	private Object lock;

	public test2(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	public void run() {
		
		synchronized (lock) {
			System.out.println("開始");
			try {
				lock.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("結束");
		}
		
		
	}

}

通知代碼

public class test3 implements Runnable{

	private Object lock;

	public test3(Object lock) {
		super();
		this.lock = lock;
	}
	@Override
	public void run() {
		
		synchronized (lock) {
			System.out.println("開始natify");
			try {
				lock.notify();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("結束notify");
		}
		
		
	}

}

調用代碼:

public static void main(String[] args) {
		Object lock = new Object();
		test2 test2 = new test2(lock);
		test2.start();
		test3 test3 = new test3(lock);
		test3.start();
	}

2.2.一、方法wait/notify/notifyAll的使用

    wait方法能夠一個參數,wait(long)方法的功能是等待某時間是否有線程對鎖進行喚醒,若是超過這個時間則自動喚醒。

lock.wait(5000);

    wait等待的條件發生了變化,也容易形成程序邏輯的 混亂。

public void add(){
		synchronized (lock) {
			 ValueObject.list.add("name");
			lock.notifyAll();
		}
	}

通知方法:

public void notifyAdd(){
		synchronized (lock) {
			if(ValueObject.list.size() == 0){
				lock.wait();
				System.out.println("等待---------");
			}
			ValueObject.list.remove(0);
		}
	}

建立兩個線程,進行調用。但第一個線程實現了減操做的線程能正確地刪除list中索引爲0的數據,但第二個線程實現減操做的線程則出現索引溢出,由於list中僅僅添加了索引爲0的數據,也只能刪除一個數據,因此沒有第二個數據可供刪除。

解決:

public void notifyAdd(){
		synchronized (lock) {
			while(ValueObject.list.size() == 0){
				lock.wait();
				System.out.println("等待---------");
			}
			ValueObject.list.remove(0);
		}
	}

總結:線程很怕通知不到,致使線程出現死鎖。

2.三、生產與消費者模式

2.3.一、一輩子產者與一消費者

生產者:

public class test2 implements Runnable{

	private Object lock;
	private String name;
	public test2(Object lock,String name) {
		super();
		this.lock = lock;
		this.name = name;
	}
	
	public void setValue(){
		try {
			synchronized (lock) {
				if(!name.equals("")){
					lock.wait();
				}
				System.out.println(name);
				lock.notify();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

消費者代碼:

public void getValue(){
		try {
			synchronized (lock) {
				if(name.equals("")){
					lock.wait();
				}
				System.out.println(name);
				lock.notify();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

2.3.二、多生產者與多消費者

    這個模式很容易假死,由於條件改變時並無獲得及時的響應時,很容易出現。把上面的if語句改成while,就是多消費者和多生產者了。解決假死很簡單就是把notify()改成notifyAll()方法。

2.3.三、生產和消費者這兩個線程交替執行(面試有的時候,會問怎麼實現)

完整代碼:

import java.util.ArrayList;
import java.util.List;

public class test3 {
	private Object lock;
	private List list = new ArrayList();
	
	public test3(Object lock) {
		super();
		this.lock = lock;
	}

	synchronized public void add(){
		try{
		if(list.size() == 1){
			this.wait();
		}
		list.add("name:"+Math.random());
		this.notify();
		System.out.println(list.size());
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	synchronized public String get(){
		String returnNamle="";
		try {
			if(list.size() == 0){
				System.out.println("線程等待");
				this.wait();
			}
			returnNamle =""+list.get(0);
			list.remove(0);
			this.notify();
			System.out.println(list.size());
		} catch (Exception e) {
			// TODO: handle exception
		}
		return returnNamle;
	}
	
	
}

多生產者和消費者:

if(list.size() == 0 )條件語句,改成while(list.size() == 0 );同時把兩處的notify()改成notifyAll()方法,不改,容易出現假死的狀態。

2.3.4 等待/通知之交備份

    案例:建立20個線程,其中10個線程數據備份到A庫,10個線程備份到B redis緩存中。

public class test4 {
	volatile private boolean prevIsA = false;
	
	synchronized public void addA(){
		try {
			while(prevIsA == true){
				wait();
			}
			for(int i=0;i<5;i++){
				System.out.println("#######");
			}
			prevIsA = true;
			notifyAll();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	synchronized public void addB(){
		try {
			while(prevIsA == false){
				wait();
			}
			for(int i=0;i<5;i++){
				System.out.println("$$$$$$");
			}
			prevIsA = false;
			notifyAll();
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	public static void main(String[] args) {
		
	}
}

注意:volatile private boolean prevIsA = false;

2.四、join方法的使用

    在不少狀況下,主線程建立並啓動子線程,若是子線程中要進行大量的耗時運算,主線程每每將早於子線程結束以前。這時,若是主線程要用到子線程的值,就要用到join()方法了。方法join()的做用是等待對象銷燬。

    join的做用是使所屬的線程對象X正常執行run()方法中的任務,而使當前線程Z進行無限期的阻塞,等待線程X銷燬後在繼續執行線程Z後面的代碼。方法join具備使線程排隊運行的做用(注意:面試的時候,怎麼使t1,t2,t3線程有序的執行,使用哪一個方法?),有些相似同步的運行效果。

    join與synchronized的區別是:join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是「對象監視器」原理做爲同步。

    在join過程當中,若是當前線程對象被中斷,則當前線程出現異常。同時join(long),可使線程等待多少時間。

    join(long)與sleep(long)的區別:join在內部使用wait()方法進行等待,因此join(long)方法具備釋放鎖的特色,那麼其餘線程就能夠調用此線程中的同步方法了;而Thread.sleep(long)方法卻不釋放鎖。

2.五、類ThreadLocal

    若是想實現每個線程都有本身的共享變量該如何解決?使用同一個變量可使用public static。ThreadLocal能夠解決上述問題。類ThreadLocal主要解決的就是每一個線程綁定本身的值,能夠將ThreadLocal類比喻成全局存放數據的盒子,盒子中能夠存儲每一個線程的私有數據。

Class test{
static ThreadLocal threadLocal =new ThreadLocal();
public static void main(String[] args) {
		threadLocal.set("12");
        threadLocal.get()
}
}

    類InheritableThreadLocal 能夠在子線程中取得父線程繼承下來的值。

2.六、線程的運行狀態

線程狀態轉換

資料連接:http://blog.csdn.net/hangelsing/article/details/44037675

相關文章
相關標籤/搜索