多線程實現、週期、控制方法

1、進程與線程區別:
在操作系統中打開一個記事本,就是啓動一個程序,代表着操作系統會分配一塊內存給這個程序進程,一個進程至少有一個線程,線程是最小的執行單元,可以共享進程的數據,開銷比較小;

打開一個程序就會開啓一個進程,一個進至少有一個線程,多個線程之間可以共享進程的數據;

2、併發與並行的區別:
並行:是指同一時刻有多個指令在處理器上執行;是同時進行;
併發:指一個時刻只能有一個指令得到執行,但是會快速的輪換執行多個指令

我們打開自己的操作系統電腦開着音樂,開着記事本 而操作系統CPU在執行時是輪換執行各個進程的命令,只是CPU的處理速度比較快,我們感覺不到;

3、創建多線程:

方法一:繼承Thread類

public class ThreadTest extends Thread {
	
   public ThreadTest(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		for (int i=0;i<10;i++) {
			System.out.println(currentThread().getName()+"  is running"+i);
		}
	   	
	}
	
	public static void main(String[] args) {
		//1------自定義類繼承Thread類
		ThreadTest newThreadTest=new ThreadTest("new Thread");
		//2------啓動線程
		newThreadTest.start();
		for (int i=0;i<10;i++) {
			System.out.println(currentThread().getName()+"  is running"+i);
		}
	   	
	}

}
main  is running0
main  is running1
main  is running2
main  is running3
main  is running4
main  is running5
main  is running6
main  is running7
main  is running8
main  is running9
new Thread  is running0
new Thread  is running1
new Thread  is running2
new Thread  is running3
new Thread  is running4
new Thread  is running5
new Thread  is running6
new Thread  is running7
new Thread  is running8
new Thread  is running9

方法二:實現Runnable接口

public class InterfaceThead  implements Runnable{

	@Override
	public void run() {
		for (int i=0;i<10;i++) {
			System.out.println("my thread "+"  is running"+i);
		}		
	}

	public static void main(String[] args) {
		//1----定義一個類實現Runnable接口並重寫其中的run方法
		InterfaceThead threadInterfaceThead=new InterfaceThead();
		//2----將runnable作爲Thread 的構造參數傳入target
		Thread tesThread=new Thread(threadInterfaceThead);
		//3-----啓動線程
		tesThread.start();
	}
}
my thread   is running0
my thread   is running1
my thread   is running2
my thread   is running3
my thread   is running4
my thread   is running5
my thread   is running6
my thread   is running7
my thread   is running8
my thread   is running9

方法三:

public class CallableThread  implements Callable<String>{

	@Override
	public String call() throws Exception {
		System.out.println("開始調用新的線程!");
		return "有返回值的線程!";
	}

	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//1-----FutureTask包裝callable對象,其中callable要重寫call方法
		FutureTask task=new FutureTask<String>(new CallableThread());
		//2-----將futureTask作爲new Thread(target,name)
		  Thread thread=new Thread(task, "callableThread");
		//3-----啓動線程
		  thread.start();
		  //4-----接收返回的值
		  if(task.get() != null) {
			  System.out.println(task.get());
		  }
	}
}

結論:無論使用哪種方式,都調用的是Thread的run方法,如果不適用方法一,不覆蓋Thread的run方法的話,使用方法二與方法三,傳入一個target則會調用Runable或者Callable的run方法:請查看源碼Thread的run方法源碼:

   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

4、線程週期:
在這裏插入圖片描述
本圖片來源於網絡

線程的5種狀態:

新建狀態:new 在使用方法new Thread()系統就會爲線程分配內存,處於就緒狀態;

就緒狀態:runnable 使用Thread 的start的方法是線程處於就緒狀態等待JVM的調用;

運行狀態:run JVM開始調用該線程

阻塞狀態:blocked 使其他的線程暫時獲得執行的資源的機會,暫且將正在執行的線程阻塞,一般是採用的搶佔式調度策略,也可以使用sleep或者yield暫且主動讓出當前的資源;

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;wait會釋放持有的鎖

2.同步阻塞 – 線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態;

3.其他阻塞 – 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

死亡狀態:線程執行完了或者因異常退出了run()方法,該線程結束生命週期

5、控制線程:
在這裏插入圖片描述
圖片來源:https://www.jianshu.com/p/3c85daeba1a5
sleep方法

/**
 * SleepTest 繼承線程Thread類,每1秒打印一次時間;
 * 主線程sleep 9秒,
 * interrupt子線程
 * 
 *
 */
public class SleepTest extends Thread {

	@Override
	public void run() {
		while(true) {
			System.out.println("now time is "+new Date());
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				System.out.println(currentThread().getName()+"is be interruped!");
				return ;
			}
			
		}
	}
	
	public static void main(String[] args) {
		SleepTest test=new SleepTest();
		test.start();
		
		try {
			Thread.sleep(9000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(currentThread().getName()+" is running!");
		test.interrupt();
	}
}
now time is Thu Sep 12 15:43:34 CST 2019
now time is Thu Sep 12 15:43:35 CST 2019
now time is Thu Sep 12 15:43:36 CST 2019
now time is Thu Sep 12 15:43:37 CST 2019
now time is Thu Sep 12 15:43:38 CST 2019
now time is Thu Sep 12 15:43:39 CST 2019
now time is Thu Sep 12 15:43:40 CST 2019
now time is Thu Sep 12 15:43:41 CST 2019
now time is Thu Sep 12 15:43:42 CST 2019
main is running!
Thread-0is be interruped!

yield方法

/**
 * yield方法調用後,線程會暫時處於就緒狀態等待JVM的調用,可能就會立即被調用
 * 
 *
 */
public class YieldTest extends Thread{

	@Override
	public void run() {
		for(int i=0;i<20;i++) {
			 System.out.println(currentThread().getName()+" is running  "+i);
			if(i%2==0) {
				Thread.yield();
			}
		}
	}
	
	
	public static void main(String[] args) {
		YieldTest test1=new YieldTest();
		YieldTest test2=new YieldTest();
		test1.start();
		test2.start();
	}
}
Thread-0 is running  10
Thread-1 is running  8
Thread-0 is running  11
Thread-0 is running  12
Thread-1 is running  9
Thread-1 is running  10
Thread-0 is running  13
Thread-0 is running  14
Thread-1 is running  11
Thread-1 is running  12
Thread-0 is running  15
Thread-0 is running  16
Thread-0 is running  17
Thread-0 is running  18
Thread-1 is running  13
Thread-1 is running  14
Thread-0 is running  19
Thread-1 is running  15
Thread-1 is running  16
Thread-1 is running  17
Thread-1 is running  18
Thread-1 is running  19

join方法

/*
 * join就是等待線程執行完畢後才能執行另一個線程
 */
public class JoinTest extends Thread {

	@Override
	public void run() {
		for(int i=0;i<10;i++) {
			System.out.println(currentThread().getName()+"  is  running "+i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		JoinTest test=new JoinTest();
		test.start();
		try {
			test.join(9000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("i am the main thread");
	}
}
Thread-0  is  running 0
Thread-0  is  running 1
Thread-0  is  running 2
Thread-0  is  running 3
Thread-0  is  running 4
Thread-0  is  running 5
Thread-0  is  running 6
Thread-0  is  running 7
Thread-0  is  running 8
i am the main thread
Thread-0  is  running 9

wait方法

/**
 * 
 * 模擬一個存錢取錢的過程:
 * 要求必須先將錢存進去才能取出
 *
 */
public class WaitTest {
	//定義一個屬性
	private int i;
	
   public static void main(String[] args) {
	 WaitTest tetsTest=new WaitTest();
	 PutInto into=new PutInto(tetsTest); 
	 PutOut out=new PutOut(tetsTest);
	 into.start();
	 out.start();
	 
  }
	
   public synchronized void putIntoMoney(){
	   //如果現在錢不是0,就要先取出來,等待其他的線程取錢,釋放鎖
	   if (i!=0) {
		try {
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	   //值爲0時需要加把錢放進去了
	   i++;
	   System.out.println("我把錢放進去了!");
	   //通知其他的可以進行取錢了
       notify();
   }
	
   public synchronized void putOutMoney() {
	   //值爲0需要先將錢放進去,等待其他線程放錢進去,先釋放鎖
         if(i==0) {
        	try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} 
         }
         //值爲1時需要加把錢放取出了
         i--;
         System.out.println("我把錢取出來了!");
       //通知其他的可以進行放錢了
         notify();
   }
}  
   //----放錢的線程--------
   class PutInto extends Thread{
	   //模擬人
	   private WaitTest per;
	   
	 public  PutInto(WaitTest per){
		  this.per=per; 
	   }
	 
	 @Override
	public void run() {
		 for (int i = 0; i < 20; i++) {
	            per.putIntoMoney();
	        }

	 }
	}
   
   
   
   //------取錢的線程-------
   
   class PutOut extends Thread{
	   //模擬人
	   private WaitTest per;
	   
	 public  PutOut(WaitTest per){
		  this.per=per; 
	   }
	 
	 @Override
	public void run() {
		 for (int i = 0; i < 20; i++) {
	            per.putOutMoney();
	        }
	 }
	}

我把錢取出來了!
我把錢放進去了!
我把錢取出來了!
我把錢放進去了!
我把錢取出來了!
。。。。。。

總結下應用吧:

join方法:
等待一個線程完畢後才能執行下一個線程,會造成線程阻塞,比如你排隊去取錢,只有上一個顧客被服務完,才能輪到你;

yield方法:
讓正在執行的線程處於阻塞狀態,可能線程調用此方法後立即獲得執行的機會,也可能會讓給優先級比它高的線程;

sleep方法:
和yield一樣不會釋放鎖,使線程處於阻塞狀態,sleep(time)超時會自動喚醒

wait方法
是在synchronized的同步代碼塊中使用
wait是Object的方法,在調用中會釋放鎖,如果多個對象對此對象進行操作就會獲得此對象的鎖;
可以使用notify喚醒在此同步監視器上的一個線程;
nitifyall喚醒在此同步監視器的所有線程;

6、線程池的啓動策略

1、線程池剛創建時,裏面沒有一個線程,任務隊列是作爲參數傳進來,不過,就算隊列裏面沒有任務,線程池也不會馬上執行他們;

2、當調用execute()方法添加一個任務時,線程池會做如下的判斷:

在這裏插入圖片描述
圖片來源與網絡

下一章節我們講解下線程的安全問題及線程池

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章