1. 程式人生 > >《實戰Java高併發程式設計》學習總結(1)

《實戰Java高併發程式設計》學習總結(1)

第1章 走入並行世界

1 併發(Concurrency)和並行(Parallelism)都可以表示兩個或多個任務一起執行。但併發偏重於多個任務交替執行,而多個任務之間有可能還是序列。並行是真正意義上的“同時執行”。

2 有關並行的兩個重要定律。Amdahl定律強調當序列比例一定時,加速比是有上限的。Gustafson定律關心的是如果可被並行化的程式碼所佔比重足夠多,那麼加速比就能隨著CPU的數量線性增長。

  • Amdahl定律,它定義了序列系統並行化後的加速比的計算公式和理論上限

                       加速比定義:   加速比  =   優化前系統耗時 / 優化後系統耗時

                       

注:n表示處理器個數,T表示時間,T1表示優化前耗時,Tn表示使用n個處理器優化後的耗時。F是程式中只能序列執行的比例。

  • Gustafson定律,用於說明處理器個數,序列比例和加速比之間的關係。

                         

3 JAVA的記憶體模型(JMM)的關鍵技術點是圍繞多執行緒的原子性,可見性和有序性建立的。原子性是指一個操作是不可中斷,即使多執行緒一起執行,該操作也不會被幹擾;可見性是指一個執行緒修改了某個共享變數,其他執行緒能否立即知道該修改。有序性是指程式碼有序執行,這個是最難的,因為為了提高CPU處理效能,指令會重排,存在亂序風險

4 java的32位系統中long型資料的讀和寫都不是原子性的,多執行緒之間會相互干擾。

 

 

 

 

 

 

第2章  Java並行程式基礎

1 執行緒是輕量級程序,是程式執行的最小單位。使用多執行緒而不是用多程序進行併發程式的設計,是因為執行緒間的切換和排程成本遠遠小於程序。

2 執行緒的基本操作

  • 新建執行緒,只要使用new關鍵字建立一個執行緒物件,並且將它start( )起來即可。

Thread t1 =  new Thread() {          

       @Override

       public void run() {

       // TODO Auto-generated method stub

                System.out.println("Hello world");

       }

};

t1.start();

注:start()會新建一個執行緒並讓這個執行緒執行run()。不要直接用run()啟動新執行緒,它只會在當前執行緒中序列執行run()中的程式碼

 

因為java是單繼承,可以採用實現介面Runnable來執行上面的步驟

public class CreateThread1 implements Runnable {

       public static void main(String[ ] args) {

              Thread t1  =  new Thread(new CreateThread1()) ;

               t1.start();

       }

       @Override

       public void run() {

       // TODO Auto-generated method stub

                System.out.println("Hello world");

       }

}

  • 終止執行緒,使用stop( ),但該函式過於暴力,容易造成資料不同步,不建議使用。
  • 執行緒中斷,在java中是一種重要的執行緒協作機制。它並不會使執行緒立即退出,而是給執行緒傳送一個通知告知目標執行緒。怎麼處理則由目標執行緒自行處理

與執行緒中斷有關的有三個方法,如下

public  void  Thread.interrupt( )       // 中斷執行緒

public  boolean  Thread.isInterrupted( )       // 判斷是否被中斷

public static boolean  Thread.interrupted( )      // 判斷是否被中斷,並清除當前中斷狀態

# interrupte() 並沒有讓t1中斷

public static void main(String[] args) throws InterruptedException{

Thread t1 = new Thread(){

         @Override

         public void run() {

          // TODO Auto-generated method stub

                     while(true){

                             System.out.println("hello");

                             Thread.yield();

                      }

           }

};

t1.start();

Thread.sleep(2000);

t1.interrupt();

}

 

# isInterrupted() 讓t1中斷

public static void main(String[] args) throws InterruptedException{

Thread t1 = new Thread(){

         @Override

         public void run() {

          // TODO Auto-generated method stub

                     while(true){

                             if(Thread.currentThread().isInterrupted()) {      // 判斷是否有被中斷,有則退出     

                                                   System.out.println("interrupted!");

                                                   break;

                             }

                             System.out.println("hello");

                             Thread.yield();

                      }

           }

};

t1.start();

Thread.sleep(2000);

t1.interrupt();

}

注:Thread.sleep( ) 方法會讓當前執行緒休眠若干時間,如果此時有個中斷它會丟擲一個InterruptedException中斷異常。

  • 等待(wait)和通知(notify),wait()和notify()並不是Thread類的,而是輸出Object類的。

public final void wait( ) throws InterruptedException

public final native void notify( )

執行緒A呼叫了obj.wait( )方法後,執行緒A會停止執行,進入object物件的等待佇列中轉為等待狀態。直到其他執行緒呼叫了obj.notify( )方法,從佇列中隨機選擇一個喚醒。wait( )不是隨便呼叫的,它必須包含在對應的synchronzied語句中,無論是wait( )或notify( )都需要首先獲得目標物件的一個監視器。

public class MultiThreadLong {

	final static Object object = new Object();
	public static class T1 extends Thread{
		public void run(){
			synchronized(object){
				System.out.println(System.currentTimeMillis()+": T1 start!");
				try{
					System.out.println(System.currentTimeMillis()+": T1 wait for object");
					object.wait();
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				System.out.println(System.currentTimeMillis()+": T1 end!");
			}
		}
	}
	public static class T2 extends Thread{
		public void run(){
			synchronized(object){
				System.out.println(System.currentTimeMillis()+": T2 start! notify one thread");
				object.notify();
				System.out.println(System.currentTimeMillis()+": T2 end!");
				try{
					Thread.sleep(2000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String[] args){
		Thread t1 = new T1();
		Thread t2 = new T2();
		t1.start();
		t2.start();
	}
}

輸出

1534760587264: T1 start!

1534760587264: T1 wait for object

1534760587264: T2 start! notify one thread

1534760587264: T2 end!

1534760589267: T1 end!           #  T1 並沒有立即繼續執行,而且等待T2釋放了object的鎖。所以它跟上一條日誌間隔2秒

注:Object.wait( )和Thread.sleep( )都可以讓執行緒等待若干時間,除了wait( )可以被喚醒外,wait( )會釋放目標物件的鎖。而sleep( )不會釋放任何資源。

  • 掛起(suspend)和繼續執行(resume)執行緒。被掛起(suspend)執行緒必須要等到resume( )操作後才能繼續執行。不推薦使用suspend()來掛起執行緒,因為它暫停同時不會釋放任何資源。
  • 等待執行緒結束(join)和謙讓(yield)

public final void join( ) throws InterruptedException     // 表示無限等待,會一直阻塞當前執行緒直到目標執行緒執行完畢

public final synchronized void join(long millis) throws InterruptedException  // 超過最大的等待時間millis後會自行繼續執行

public static native void yield( ) ;  // 靜態方法,一旦執行會使當前執行緒讓出CPU。讓出不代表不執行,會試著去搶CPU資源

    public volatile static int i = 0;
    public static class AddThread extends Thread{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(i=0;i<100000000;i++);
		}
		
	}
	public static void main(String[] args){
		AddThread at = new AddThread();
		at.start();
		try{
			at.join();
		}catch(InterruptedException e){
			
		}
		System.out.println(i);
	}

主函式中,如果不使用join()等待AddThread,輸出0。但使用join()函式後表示主執行緒願意等待AddThread執行完畢後再一起往前走。所以輸出100000000。

注:join()的本質是讓呼叫執行緒wait()在當前執行緒物件例項上,讓呼叫的執行緒在當前執行緒物件上進行等待。當執行緒執行完成後,被等待的執行緒會在退出前呼叫notifyAll( )通知所有等待的執行緒繼續執行。

3 用volatile申明變數時,表示該變數可能會被某些程式或者執行緒修改。為了確保這個變數被修改後,虛擬機器會採用一些特殊的手段保證該變數的可見性。

    static volatile int i = 0;
	public static class PlusTask implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(int k=0;k<10000;k++)
				i++;
		}
		
	}
	public static void main(String[] args) throws InterruptedException{
		Thread[] threads = new Thread[10];
		for(int i=0;i<10;i++){
			threads[i] = new Thread(new PlusTask());
			threads[i].start();
		}
		for(int i=0;i<10;i++)
			threads[i].join();
		System.out.println(i);
	}

如果 i++是原子性,最終值會是100000,但通過volatile是無法保證i++的原子性操作,所以輸出總是小於100000。

volatile能保證資料的可見性和有序性,如下所示,如果ready值沒有宣告為volatile屬性時,ready=true的賦值ReaderThread執行緒無法收到。

    private static volatile boolean ready;
	private static int number;
	
	public static class ReaderThread extends Thread{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(!ready);
			System.out.println(System.currentTimeMillis()+": "+number);
		}
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println(System.currentTimeMillis()+": 1");
		new ReaderThread().start();
		Thread.sleep(1000);
		System.out.println(System.currentTimeMillis()+": 2");
		number = 42;
		ready = true;
		Thread.sleep(1000);
		System.out.println(System.currentTimeMillis()+": 3");
	}

4 分門別類的管理:執行緒組,在一個系統中如果執行緒數量很多且功能分配明確,可以將相同功能的執行緒放在一個執行緒組中如下

public class MultiThreadLong implements Runnable {

	public static void main(String[] args){
		ThreadGroup tg = new ThreadGroup("PrintGroup");  // 建立一個"PrintGroup"的執行緒組
		Thread t1 = new Thread(tg,new MultiThreadLong(),"T1");
		Thread t2 = new Thread(tg,new MultiThreadLong(),"T2");
		t1.start();
		t2.start();
		System.out.println(tg.activeCount()); // 獲得活動執行緒的總數
		tg.list();  // 列印執行緒組的所有執行緒資訊

	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		String groupAndName = Thread.currentThread().getThreadGroup().getName()
				+"-"+Thread.currentThread().getName();
		while(true){
			System.out.println("I am "+groupAndName);
			try{
				Thread.sleep(3000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
	
}

輸出

2

I am PrintGroup-T1

I am PrintGroup-T2

java.lang.ThreadGroup[name=PrintGroup,maxpri=10]

    Thread[T1,5,PrintGroup]

    Thread[T2,5,PrintGroup]

I am PrintGroup-T1

I am PrintGroup-T2

I am PrintGroup-T1

I am PrintGroup-T2

I am PrintGroup-T1

I am PrintGroup-T2

I am PrintGroup-T1

I am PrintGroup-T2

I am PrintGroup-T1

I am PrintGroup-T2

5 駐守後臺:守護執行緒(Daemon),系統的守護者,在後臺默默地完成一些系統性的服務,比如垃圾回收執行緒,JIT執行緒等。

public class DaemonDemo {
	public static class DaemonT extends Thread{

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(true){
				System.out.println("I am alive");
				try{
					Thread.sleep(1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
		
	}
	public static void main(String[] args) throws InterruptedException{
		Thread t = new DaemonT();
		t.setDaemon(true);  // 設定為守護執行緒,但必須要在start()呼叫之前
		t.start();
		Thread.sleep(2000);
	}
}

t被設定為守護執行緒,系統只有主執行緒main為使用者執行緒,因此在main執行緒休眠2秒後退出時,整個程式也會結束,t也結束。如果不把執行緒t設定為守護執行緒,main執行緒結束後,t執行緒還好不停地列印,永遠不會結束。

6 java中的執行緒都可以有自己的優先順序,使用1到10表示優先順序,有三個內建的靜態標量表示:

  • public final static int MIN_PRIORITY = 1    // 優先順序最低
  • public final static int NORM_PRIORITY = 5
  • public final static int MAX_PRIORITY = 10   // 優先順序最高
public class PriorityDemo {

	public static class HightPriority extends Thread{
		static int count = 0;

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(true){
				synchronized(PriorityDemo.class){
					count++;
					if(count > 10000000){
						System.out.println("HightPriority is complete");
						break;
					}
				}
			}
		}
		
	}
	public static class LowPriority extends Thread{
		static int count = 0;

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while(true){
				synchronized(PriorityDemo.class){
					count++;
					if(count > 10000000){
						System.out.println("LowPriority is complete");
						break;
					}
				}
			}
		}
	}
	public static void main(String[] args){
		Thread high = new HightPriority();
		LowPriority low = new LowPriority();
		high.setPriority(Thread.MAX_PRIORITY);  // 設定優先順序
		low.setPriority(Thread.MIN_PRIORITY);   // 設定優先順序
		low.start();
		high.start();
	}
}

high優先順序高,所以在多數情況下會比low快,但不是每次都比low快

7 關鍵字synchronized的作用是實現執行緒間的同步。它的工作是對同步的程式碼加鎖,使得每次只有一個執行緒進入同步塊。主要有如下幾種用法

  • 指定加鎖物件:對給定的物件加鎖,進入同步程式碼前要獲得給定物件的鎖
  • 直接作用於例項方法:相當於對當前例項加鎖,進入同步程式碼前要獲得當前例項的鎖
  • 直接作用於靜態方法:相當於對當前類加鎖,進入同步程式碼前要獲得當前類的鎖
public class AccountingVol implements Runnable {

	static AccountingVol instance = new AccountingVol();
	static int i = 0;
	public static void increase(){
		i++;
	}
	public synchronized void increase2(){   // 第一種方法,函式定義為同步
		i++;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int j=0;j<10000000;j++){
			// 第二種方法,物件定義為同步
			synchronized(instance){
				i++;
			}
			
			//increase2();
		}
	}
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}

}

8 ArrayList是一個執行緒不安全的容器。如果多執行緒使用ArrayList可能會導致程式出錯。可以使用執行緒安全的Vector來代替ArrayList

9 HashMap也是執行緒不安全,多執行緒訪問HashMap也會導致程式出錯。可以使用ConcurrentHashMap來代替HashMap

10 一個錯誤的加鎖,如下所示,給i加鎖,但因為i是Integer物件,是不變物件,每次的值變其實都是新建一個Integer物件,即鎖加在了不同的物件上。可以修改為synchronized(instance)即可

public class BadLockOnInteger implements Runnable {

	public static Integer i = 0;
	static BadLockOnInteger instance = new BadLockOnInteger();
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int j=0;j<10000000;j++){
			synchronized(i){
				i++;
			}
		}
	}
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(i);
	}

}