1. 程式人生 > >Java併發學習(一)

Java併發學習(一)

1.要響應執行緒中斷

執行緒接受到中斷訊號後要及時的對中斷進行響應。響應方式:

1.捕捉InterruptException

	for(;;){
		try {
			doXXX();
		} catch (InterruptedException e) {
			System.out.println(getName() +" is interrupt");
			break;
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

2.判斷當前執行緒狀態
      for(;;){
		doXXX();
		//判斷是否被中斷
		if(Thread.interrupted()){
			System.out.println(getName() +" is interrupt");
			break;
		}
	}

接收到中斷訊號後,需要結束當前執行緒,可行的方式有return,break等,比較優雅的方式是丟擲InterruptedException異常。程式碼如下:

public void foo() throws InterruptedException {
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
}

2.使用ThreadLocal

顧名思義它是local variable(執行緒區域性變數)。它的功用非常簡單,就是為每一個使用該變數的執行緒都提供一個變數值的副本,是每一個執行緒都可以獨立地改變自己的副本,而不會和其它執行緒的副本衝突。從執行緒的角度看,就好像每一個執行緒都完全擁有該變數。
  • 不使用ThreadLocal的測試結果
private int sum2 = 0;
	public int getSum(){
		sum2++;
		return sum2;
	}
	public static void main(String[] args){
		new CurrentTest().threadLocal();
	}
	
	public void threadLocal(){
		TestThreadLocal t1 = new TestThreadLocal(this);
		TestThreadLocal t2 = new TestThreadLocal(this);
		TestThreadLocal t3 = new TestThreadLocal(this);
		t1.setName("t1");
		t2.setName("t2");
		t3.setName("t3");
		t1.start();
		t2.start();
		t3.start();
	}
	class TestThreadLocal extends Thread{
		CurrentTest c;
		public TestThreadLocal(CurrentTest c) {
			this.c = c;
		}
		@Override
		public void run() {
			int sum = this.c.getSum();
			while(sum<10){
				System.out.println(getName()+" sum is "+sum);
				sum = this.c.getSum();
			}
		}
	}
輸出結果:出現併發問題
  • 使用ThreadLocal的測試結果
private ThreadLocal<Integer> sum = new ThreadLocal<Integer>(){
		protected Integer initialValue() {return 0;};
	};
	public int getSum(){
		sum.set(sum.get() + 1);
		return sum.get();
	}
	public static void main(String[] args){
		new CurrentTest().threadLocal();
	}
	
	public void threadLocal(){
		TestThreadLocal t1 = new TestThreadLocal(this);
		TestThreadLocal t2 = new TestThreadLocal(this);
		TestThreadLocal t3 = new TestThreadLocal(this);
		t1.setName("t1");
		t2.setName("t2");
		t3.setName("t3");
		t1.start();
		t2.start();
		t3.start();
	}
	class TestThreadLocal extends Thread{
		CurrentTest c;
		public TestThreadLocal(CurrentTest c) {
			this.c = c;
		}
		@Override
		public void run() {
			int sum = this.c.getSum();
			while(sum<10){
				System.out.println(getName()+" sum is "+sum);
				sum = this.c.getSum();
			}
		}
	}
輸出結果:每個執行緒都從1一直計數到9,執行緒間沒有出現併發問題
事實上ThreadLocal是犧牲空間來減少高併發所消耗的時間,其原理是每個Thread維護一個Map集合,集合的Key是ThreadLocal物件,value是共享變數的副本,這樣每次Thread修改變數時就會直接修改本地儲存的變數副本。具體ThreadLocal原始碼如下:
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
注意:使用ThreadLocal,一般都是宣告在靜態變數中,如果不斷的建立ThreadLocal而且沒有呼叫其remove方法,將會導致記憶體洩露。

3.任務的提交者和執行者

為了方便併發執行任務,出現了一種專門用來執行任務的實現,也就是Executor。由此,任務提交者不需要再建立管理執行緒,使用更方便,也減少了開銷。
java.util.concurrent.Executors是Executor的工廠類,通過Executors可以建立你所需要的Executor。
常用的子類如下圖:(轉自:http://blog.csdn.net/qq_29882587/article/details/78658675) 更廣泛的使用是ExecutorSevice介面,主要API如下: 其中submit方法可以接收兩類引數,Runable和Callable,Callable是需要有返回值的。submit方法返回Feture物件。Feture物件用於執行緒間的通訊,Future通常包括get(阻塞至任務完成), cancel,get(timeout)(等待一段時間)等等。Future也用於非同步變同步的場景。