多執行緒複習

wit_stan發表於2018-08-20

概念:

    如果一個 程式中同時執行了多個執行緒,用來完成不同的工作,則稱為多執行緒。

   多個執行緒交替的佔用CPU資源,而非真正的並行執行。

好處:

   充分利用CPU資源

   簡化程式設計模型

   帶來良好的使用者體驗

1、獲取當前執行緒

public class ThreadDemo {
   public static void main(String[] args) {
	  //獲取當前執行緒
	  Thread t=Thread.currentThread();
	  System.out.println("當前執行緒是"+t.getName());
	  t.setName("我是Java主執行緒");
	  System.out.println("當前執行緒的名字是:"+t.getName());
}
}

2、實現執行緒的三種方法:

1、繼承Thread的方式實現執行緒

      Thread類本質上是實現了Runnable介面的一個例項,代表一個執行緒的例項。啟動執行緒的唯一方法就是通過Thread類的start()例項方法。start()方法是一個native方法,它將啟動一個新執行緒,並執行run()方法。這種方式實現多執行緒很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啟動新執行緒並執行自己定義的run()方法。

public class MyThreadDemo {
	public static void main(String[] args) {
         MyThread thread1 = new MyThread(); //建立執行緒
         MyThread thread2 = new MyThread();
		 thread1.start();
		 thread2.start();
		 for(int i=0;i<100;i++) {
		  	   System.out.println("main主執行緒"+i);
		     }
	}

class MyThread extends Thread{
	
	//重寫run方法
	@Override
	public void run(){
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
}

}

}

 執行結果:

main主執行緒0
Thread-1:0
Thread-0:0
Thread-1:1
main主執行緒1
Thread-1:2
Thread-0:1
Thread-1:3
main主執行緒2
Thread-1:4
Thread-0:2
Thread-1:5
main主執行緒3
Thread-1:6
Thread-0:3
.......

2、實現Runnable介面實現執行緒

     如果自己的類已經extends另一個類,就無法直接extends Thread,此時,可以實現一個Runnable介面

public class MyRunnableDemo {
   public static void main(String[] args) {
	 MyRunnable myRunnable = new MyRunnable();
	 //使用靜態代理
	 Thread thread = new Thread(myRunnable);
	 thread.start();
	 for(int i=0;i<100;i++) {
  	   System.out.println("main主執行緒"+i);
     }
  }

class MyRunnable implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}	
	}

}
}

為了啟動MyRunnable ,需要首先例項化一個Thread,並傳入自己的MyRunnable 例項,這裡用到了靜態代理。

執行結果:

main主執行緒0
Thread-0:0
main主執行緒1
Thread-0:1
main主執行緒2
Thread-0:2
main主執行緒3
Thread-0:3
main主執行緒4
Thread-0:4
main主執行緒5
Thread-0:5
main主執行緒6
Thread-0:6
main主執行緒7
Thread-0:7
main主執行緒8
Thread-0:8
main主執行緒9

3、實現Callable介面建立Thread執行緒,然後該類重寫 Callable這個介面裡的抽象方法call

Callable介面(也只有一個方法)原始碼如下:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable 介面類似於 Runnable,兩者都是為那些其例項可能被另一個執行緒執行的類設計的。但是 Runnable 不會返回結果,並且無法丟擲經過檢查的異常。

案例:通過實現Callable這個介面,來實現多執行緒,建立一個求和類;

具體的多執行緒實現程式碼如下:

package com.stan.thread;
/**
 *實現Callable介面實現執行緒 
 */
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
	  
	  //建立執行緒池,執行緒池裡面可以養兩個執行緒
      ExecutorService pool  = Executors.newFixedThreadPool(2);
      //由於下面建立的類中裡面有一個有參構造方法,這裡得傳一個引數
      //submit返回一個Future,所以說,應該用Future去接收
	  //Future可以獲取出來1-100或者是1-50的求和結果
	  Future<Integer> f1 = pool.submit(new MyCallable(100));      
	  Future<Integer> f2 = pool.submit(new MyCallable(50));
	  //用get()方法獲取返回結果
	  System.out.println(f1.get());
	  System.out.println(f2.get());
	 //V get()   throws InterruptedException,ExecutionException如有必要,
	  //等待計算完成,然後獲取其結果。 
	  pool.shutdown();  //關閉執行緒池
	    }

	}		
//建立一個MyCallable類去實現Callable這個介面	
class MyCallable implements Callable<Integer>{       
     
	 //設定一個成員變數num
	 private int num; 
	 //建立一個有參構造方法 
     public MyCallable (int num){
     //num是傳進去的數
	   this.num = num;                                            
    }
     
       //重寫Callable這個介面裡面的抽象方法call;這裡的返回值類跟泛型<Integer>是一致的
	    @Override
	    public Integer call() throws Exception {                        
	        int sum = 0;   //實現求和程式碼                                             
	        for (int i = 1; i <= num; i++) {
	            sum += i;
	        }

	        return sum;  //返回sum                                               
	    }
	}

3、執行緒常用的方法

       1、sleep()

        使當前執行緒(即呼叫該方法的執行緒)暫停執行一段時間,讓其他執行緒有機會繼續執行,但它並不釋放物件鎖。也就是說如果有synchronized同步快,其他執行緒仍然不能訪問共享資料。注意該方法要捕捉異常。例如有兩個執行緒同時執行(沒有synchronized)一個執行緒優先順序為MAX_PRIORITY,另一個為MIN_PRIORITY,如果沒有Sleep()方法,只有高優先順序的執行緒執行完畢後,低優先順序的執行緒才能夠執行;但是高優先順序的執行緒sleep(500)後,低優先順序就有機會執行了。總之,sleep()可以使低優先順序的執行緒得到執行的機會,當然也可以讓同優先順序、高優先順序的執行緒有執行的機會。

package cn.guan.thread.status;
/**
 * 測試Sleep()
 * 1秒輸出一次數字
 * @author 關啟培
 *
 */
public class SleepThreads {
	public static void main(String[] args) throws InterruptedException {
		int num=0;
		while(true) {
			System.err.println("學習了"+(num++)+"秒");
			Thread.sleep(1000);
			
		}
	}

 

2、join()

       join()方法使呼叫該方法的執行緒執行完畢,也就是等待該方法的執行緒執行完畢後再往下繼續執行。注意該方法也需要捕捉異常。

package cn.guan.thread.status;
/**
 * join()方法測試 
 * @author 
 *
 */
public class JoinThread  extends Thread{
	@Override
	public void run() {
	    for(int i=0;i<200;i++) {
	    	System.out.println("join ...."+i);
	    }
	}	
	public static void main(String[] args) throws InterruptedException {
		JoinThread s=new JoinThread();
			Thread t=new Thread(s);
		    t.start();
	
		 for(int i=0;i<100;i++) {
			 if(i==60) {
				//t執行緒加入到main執行緒中來,只有t執行緒執行結束,才會繼續往下走
				 t.join();      
			 }
			//會觀察到直到t執行緒執行完才執行main執行緒
		   System.out.println("main-->"+i);
	  }
		 
		 
	}
}




3、yield()

當前執行緒,臨時暫停,使得其他執行緒可以有更多的機會佔用CPU資源,該方法與sleep()類似,只是不能由使用者指定暫停多長時間,並且yield()方法只能讓同優先順序的執行緒有執行的機會。

package com.stan.thread;
/**
 * 執行緒禮讓
 * @author guan
 *
 */
public class MyThread02 implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<10;i++) {
			System.out.println("執行緒禮讓:"+i);
			if(i==6){
				//當i為6時,當前執行緒,臨時暫停,使得其他執行緒佔用CPU資源
				Thread.yield();
			}
		}	
	}
   public static void main(String[] args) {
	 MyThread02 thead1 = new MyThread02();
	 Thread proxy = new Thread(thead1);
	 proxy.start();
	 for(int i=0;i<15;i++) {
		 System.out.println("主執行緒:"+i);
	 }
}
}

4、wait()和notify()、notifyAll()

        這三個方法用於協調多個執行緒對共享資料的存取,所以必須在synchronized語句塊內使用。synchronized關鍵字用於保護共享資料,阻止其他執行緒對共享資料的存取,但是這樣程式的流程就很不靈活了,如何才能在當前執行緒還沒退出synchronized資料塊時讓其他執行緒也有機會訪問共享資料呢?此時就用這三個方法來靈活控制。

       wait()方法使當前執行緒暫停執行並釋放物件鎖標示,讓其他執行緒可以進入synchronized資料塊,當前執行緒被放入物件等待池中當呼叫notify()方法後,將從物件的等待池中移走一個任意的執行緒並放到鎖標誌等待池中,只有鎖標誌等待池中執行緒能夠獲取鎖標誌;如果鎖標誌等待池中沒有執行緒,則notify()不起作用。

        notifyAll()則從物件等待池中移走所有等待那個物件的執行緒並放到鎖標誌等待池中。

注意 這三個方法都是java.lang.Object的方法。

二、run和start()

      把需要處理的程式碼放到run()方法中,start()方法啟動執行緒將自動呼叫run()方法,這個由java的記憶體機制規定的。並且run()方法必需是public訪問許可權,返回值型別為void。

 

三、關鍵字synchronized

      該關鍵字用於保護共享資料,當然前提條件是要分清哪些資料是共享資料。每個物件都有一個鎖標誌,當一個執行緒訪問到該物件,被Synchronized修飾的資料將被"上鎖",阻止其他執行緒訪問。當前執行緒訪問完這部分資料後釋放鎖標誌,其他執行緒就可以訪問了。

package com.stan.thread;
/**
 * 模擬搶票過程
 * @author guan
 *
 */
public class Size implements Runnable {
	//count就是共享資料
	private int count=20; 
    
    private int num=0;

	@Override
	public void run() {
        while(true){
        	//同步程式碼塊
        	synchronized (this) {
				if(count<=0) {
					break;
				}
				//修改資料
				num++;
				count--;
				try {
					//讓當前執行緒休眠0.5秒
					Thread.sleep(500);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"搶到第"+num+
						"張票,剩餘"+count+"張");
			}
        }		
	}
    
    public static void main(String[] args) {
	  Size size = new Size();
	  Thread person1 = new Thread(size,"關啟培");
	  Thread person2 = new Thread(size,"搶票代理");
	  Thread person3 = new Thread(size,"黃牛");
	  System.out.println("************開始搶票*************");
	  person1.start();
	  person2.start();
	  person3.start();
	}

}

執行結果:

************開始搶票*************
關啟培搶到第1張票,剩餘19張
黃牛搶到第2張票,剩餘18張
搶票代理搶到第3張票,剩餘17張
黃牛搶到第4張票,剩餘16張
關啟培搶到第5張票,剩餘15張
黃牛搶到第6張票,剩餘14張
黃牛搶到第7張票,剩餘13張
搶票代理搶到第8張票,剩餘12張
黃牛搶到第9張票,剩餘11張
黃牛搶到第10張票,剩餘10張
關啟培搶到第11張票,剩餘9張
黃牛搶到第12張票,剩餘8張
黃牛搶到第13張票,剩餘7張
搶票代理搶到第14張票,剩餘6張
黃牛搶到第15張票,剩餘5張
關啟培搶到第16張票,剩餘4張
關啟培搶到第17張票,剩餘3張
黃牛搶到第18張票,剩餘2張
黃牛搶到第19張票,剩餘1張
搶票代理搶到第20張票,剩餘0張

 

四、wait()和notify(),notifyAll()是Object類的方法,sleep()和yield()是Thread類的方法。

(1)、常用的wait方法有wait()和wait(long timeout);

void wait() 在其他執行緒呼叫此物件的 notify() 方法或者 notifyAll()方法前,導致當前執行緒等待。

void wait(long timeout)在其他執行緒呼叫此物件的notify() 方法 或者 notifyAll()方法,或者超過指定的時間量前,導致當前執行緒等待。

wait()後,執行緒會釋放掉它所佔有的“鎖標誌”,從而使執行緒所在物件中的其他shnchronized資料可被別的執行緒使用。

wait()h和notify()因為會對物件的“鎖標誌”進行操作,所以他們必需在Synchronized函式或者 synchronized block 中進行呼叫。如果在non-synchronized 函式或 non-synchronized block 中進行呼叫,雖然能編譯通過,但在執行時會發生IllegalMonitorStateException的異常。。

 

(2)、Thread.sleep(long millis)必須帶有一個時間引數。

sleep(long)使當前執行緒進入停滯狀態,所以執行sleep()的執行緒在指定的時間內肯定不會被執行;

sleep(long)可使優先順序低的執行緒得到執行的機會,當然也可以讓同優先順序的執行緒有執行的機會;

sleep(long)是不會釋放鎖標誌的。

(3)、yield()沒有引數

sleep 方法使當前執行中的執行緒睡眠一段時間,進入不可以執行狀態,這段時間的長短是由程式設定的,yield方法使當前執行緒讓出CPU佔有權,但讓出的時間是不可設定的。

yield()也不會釋放鎖標誌。

實際上,yield()方法對應瞭如下操作;先檢測當前是否有相同優先順序的執行緒處於同可執行狀態,如有,則把CPU的佔有權交給次執行緒,否則繼續執行原來的執行緒,所以yield()方法稱為“退讓”,它把執行機會讓給了同等級的其他執行緒。

sleep 方法允許較低優先順序的執行緒獲得執行機會,但yield()方法執行時,當前執行緒仍處在可執行狀態,所以不可能讓出較低優先順序的執行緒此時獲取CPU佔有權。在一個執行系統中,如果較高優先順序的執行緒沒有呼叫sleep方法,也沒有受到I/O阻塞,那麼較低優先順序執行緒只能等待所有較高優先順序的執行緒執行結束,方可有機會執行。

yield()只是使當前執行緒重新回到可執行狀態,所有執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行,所以yield()方法只能使同優先順序的執行緒有執行的機會。

 

相關文章