1. 程式人生 > >【多執行緒】執行緒通訊之join、ThreadLocal

【多執行緒】執行緒通訊之join、ThreadLocal

一、方法join

       在很多情況下,主執行緒建立並啟動子執行緒,如果子執行緒中需要進行大量的耗時運算,主執行緒往往將早於子執行緒結束之前結束。如果主執行緒想等待子執行緒執行完成之後在結束,比如子執行緒執行一個方法,主執行緒要取得這個方法的返回值,就要用到join()方法了。方法join的作用是等待執行緒物件銷燬。執行緒Thread除了提供join方法之外,還提供了join(long  millis)和join(long millis,int nanos)兩個具有超時特性的方法。這兩個超時方法表示,如果執行緒thread在給定的超時時間內沒有銷燬,那麼將從該超時方法中返回。

1、簡單demo

public class MyThread extends Thread {

	@Override
	public void run(){
		try {
			int secondValue=(int)(Math.random()*10000);
			System.out.println(secondValue);
			Thread.sleep(secondValue);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		try {
		MyThread threadTest=new MyThread();
		threadTest.start();
		//Thread.sleep(?)
		threadTest.join();
		System.out.println("我想當threadTest物件執行完畢後我再執行,我做到了");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}
     輸出結果如下:

                

       方法join的作用是使所屬的執行緒物件x正常執行run方法中的任務,而使當前執行緒z進入阻塞狀態,等待執行緒x銷燬後再繼續執行執行緒z後面的程式碼。

       方法join具有使執行緒排隊執行的作用,有些類似同步的效果。join與synchronized的區別是:join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是“物件監視器”原理作為同步。

2、使執行緒按順序執行

      之前說過執行緒呼叫具有“無序性”的特點,那麼  有三個執行緒T1,T2、T3,怎麼確保它們按順序執行?

public class Join {
	public static void main(String[] args) {
		Thread previous=Thread.currentThread();
		for (int i = 0; i < 10; i++) {
			//每個執行緒擁有前一個執行緒的引用,需要等待前一個執行緒終止,才能從等待中返回
			Thread thread=new Thread(new Domino(previous),String.valueOf(i));
			thread.start();
			previous=thread;
		}
	}
	//靜態內部類
	static class Domino implements Runnable{
		private Thread thread;
		public Domino(Thread thread){
			this.thread=thread;
		}
		public void run(){
			try {
				thread.join();
			} catch (Exception e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" terminate.");
		}
	}

}
     

       上述程式碼,建立類10個執行緒,編號0~9,每個執行緒呼叫前一個執行緒的join方法,也就是執行緒0結束了,執行緒1才能從join方法中返回,而執行緒0需要等待main執行緒結束。每個執行緒終止的前提是前驅執行緒的終止,每個執行緒等待前驅執行緒終止後,才從join方法返回,本質上仍是等待/通知機制(等待前驅執行緒結束,結束前驅執行緒結束通知)

        join方法原始碼如下:

 public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

二、ThreadLocal

       之前【多執行緒】volatile關鍵字中提及了記憶體模型具有原子性可見性有序性

       Thread顧名思義可以理解為執行緒本地變數,是一個以ThreadLocal物件為鍵,任意物件為值的儲存結構。這個結構被附帶線上程上,也就是說一個執行緒可以根據一個ThreadLocal物件查詢到繫結在這個執行緒上的一個值。換句話說就是每個執行緒繫結自己的值。每個執行緒往這個ThreadLocal中讀寫是執行緒隔離的,互相之間不會影響

        通過set(T)方法來設定一個值,在當前執行緒下通過get方法獲取到原先設定的值。

public class Run {
	public static ThreadLocal t1=new ThreadLocal();
	public static void main(String[] args) {
		if (t1.get()==null) {
			System.out.println("從未放過值");
			t1.set(" 我的值 ");
		}
		System.out.println(t1.get());
	}

}
     接下來驗證執行緒的隔離性。
public class Tools {
	/**
	 * 驗證執行緒變數的隔離性
	 */
	public static ThreadLocal t1=new ThreadLocal();
}
    
public class ThreadA extends Thread {
	@Override
	public void run(){
		try {
			for (int i = 0; i < 100; i++) {
				Tools.t1.set("ThreadA"+(i+1));
				System.out.println("ThreadA get Value="+Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

}
     
public class ThreadB extends Thread {
	@Override
	public void run(){
		try {
			for (int i = 0; i < 100; i++) {
				Tools.t1.set("ThreadB"+(i+1));
				System.out.println("ThreadB get Value="+Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

}
     
public class Run {
	public static void main(String[] args) {
		try {
			ThreadA a=new ThreadA();
			ThreadB b=new ThreadB();
			a.start();
			b.start();
			for (int i = 0; i < 100; i++) {
				Tools.t1.set("Main"+(i+1));
				System.out.println("Main get Value="+Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
     

     雖然3個執行緒都向t1物件中set資料值,但每個執行緒都能取值自己對應的資料。(t1物件是單例的)

     ThreadLocal和synchronized都能保證執行緒安全,synchronized是用時間換空間,synchronized是用空間換時間。