JavaSE--重新學習_查漏補缺(16)--多線程創建和啓動_Thread類的有關方法_線程的生命週期_線程的同步_線程的死鎖問題_線程間通信_經典線程同步---生產者/消費者問題

一、多線程

形象理解====多線程相當進程的支流,各走各的,假設在進程上跑的代碼是主程序,當其中的第三行代碼是開啓線程的,那麼開啓線程之後 ,線程運行的代碼就和主程序並行(即它們之間不相干了)

二、多線的創建和啓動

  • Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread類和java.util.concurrent併發包下的Callable接口來實現。
    創建線程有三種方式:
  1. 繼承Thread類
  2. 實現Runnable接口
  3. 創建一個類實現Callable接口(本次不重點說這一個)
  • 以上三種方式的前兩種都是通過Thread類實現的,它們的啓動方式如下:
    創建對象Thread對象->調用start()->相當於調用run(),啓動線程。
    要實現線程代碼的邏輯,只需將代碼在run()方法中即可。

  • 兩者區別:

    1. 繼承Thread: 線程代碼存放Thread子類run方法中。重寫run方法
    2. 實現Runnable:線程代碼存在接口的子類的run方法。實現run方法
  • 實現接口的好處:

    1. 避免了單繼承的侷限性
    2. 多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。
      所以一般使用實現接口方式來實現多線程
  • 兩種線程實現方法代碼:

  • 繼承Thread方式

package ThreadDemo;

/**
 * 繼承Thread實現多線程
 */

public class TestThrad extends Thread {
    @Override
    public void run(){
        System.out.println("多線程運行的代碼");
        for(int i= 0 ; i<6 ; i++){
            System.out.println("這時多線程的邏輯代碼");
        }
    }
}

  • 實現Runnable接口
package ThreadDemo;

public class TestRunable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Runable接口多線程運行的代碼");
        for(int i= 0 ; i<6 ; i++){
            System.out.println(Thread.currentThread().getName()+"這是Runable接口多線程的邏輯代碼");
        }
    }
}
  • 測試類
package ThreadDemo;

public class Test {
    public static void main(String[] args) {
//        Thread to = new TestThrad();
//        to.start();

        Thread rt = new Thread(new TestRunable());
        Thread rt1 = new Thread(new TestRunable(),"t-1");

        rt.start();
        rt1.start();

        for(int i= 0 ; i<5 ; i++){
            System.out.println("傘兵一號準備就緒");
            System.out.println("我宣佈");
            System.out.println("現在這裏叫做盧本偉廣場");
            System.out.println("傘兵一號準備就緒");
            System.out.println("我宣佈");
            System.out.println("現在這裏叫做盧本偉廣場");
        }
    }
}

三、Thread類的有關方法

  1. void start(): 啓動線程,並執行對象的run()方法
  2. run(): 線程在被調度時執行的操作
  3. String getName(): 返回線程的名稱
  4. void setName(String name):設置該線程名稱
  5. static currentThread(): 返回當前線程
  6. static void yield():線程讓步
    暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程
    若隊列中沒有同優先級的線程,忽略此方法
  7. join() :當某個程序執行流中調用其他線程的 join() 方法時,調用線程將被阻塞,直到 join() 方法加入的 join 線程執行完爲止
    低優先級的線程也可以獲得執行
  8. static void sleep(long millis):(指定時間:毫秒)
    令當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重排隊。
    拋出InterruptedException異常
  9. stop(): 強制線程生命期結束
  10. boolean isAlive():返回boolean,判斷線程是否還活着
  11. 線程的優先級控制
    MAX_PRIORITY(10);
    MIN _PRIORITY (1);
    NORM_PRIORITY (5);
    涉及的方法:
    getPriority() :返回線程優先值
    setPriority(int newPriority) :改變線程的優先級
    線程創建時繼承父線程的優先級,默認都是5,數值越高優先級越高。
    但不是優先級越高就會先執行,這東西就是概率性的東西,優先級越高被執行的概率越高。
    以下代碼對上述方法都有實現。
public class Test1 {
	public static void main(String[] args) {
		TestRun run0 = new TestRun();
		TestRun run1 = new TestRun();
		
		Thread t0 = new Thread(run0);
		
		Thread t1 = new Thread(run1);
		
		t0.setName("線程t-0");//設置線程的名稱
		t1.setName("線程t-1");//設置線程的名稱
		
//		t0.setPriority(1);//設置線程的優先級
//		t1.setPriority(10);//設置線程的優先級
		
		t0.start();
		t1.start();
//		System.out.println(t0.getName());//如果在創建線程的時候沒有指定名稱,系統會給出默認名稱,通過getName()獲取線程名稱
//		System.out.println(t1.getName());
		
		/**
		 * 線程的優先級,就是哪個線程有較大個概率被執行
		 * 優先級是用數組1-10表示,數字越大優先級越高,如果沒有設置默認優先級是5
		 */
		
//		System.out.println("t0的優先級:" + t0.getPriority());//獲取線程的優先級
		
		System.out.println("---------------1");
		System.out.println("---------------2");
		
		System.out.println(t1.isAlive());//判斷當前的線程是否存活
		
		t1.stop();//強制線程生命期結束,強制停止此線程
		
		try {
			t0.join();//相當於在這塊把t0的run的代碼插入到這個位置執行
			/**
			 * 專業的說法
			 * 就是阻塞當前的main方法,先不執行System.out.println("---------------3");代碼
			 * 先執行join進來的線程的代碼
			 * join的線程執行完畢之後繼續執行之前main方法阻塞的代碼System.out.println("---------------3");
			 */
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		
		System.out.println("---------------3");
		
		System.out.println(t1.isAlive());
	}
}

class TestRun implements Runnable{
	int count = 0;
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + ":Runnable多線程運行的代碼");
		for(int i = 0; i < 5; i++){
//			try {
//				Thread.sleep(1000);//當前線程睡眠1000毫秒
//				//相當於當前的這個循環每隔1000毫秒執行一次循環
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			
//			if(i % 2 == 0){
//				Thread.yield();//線程讓步
//			}
			
			count++;
			System.out.println(Thread.currentThread().getName() + ":這是Runnable多線程的邏輯代碼:" + count);
		}
	}
	
}

四、線程的生命週期

JDK中用Thread.State枚舉表示了線程的幾種狀態
要想實現多線程,必須在主線程中創建新的線程對象。Java語言使用Thread類及其子類的對象來表示線程,在它的一個完整的生命週期中通常要經歷如下的五種狀態:

  1. 新建: 當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建狀態
  2. 就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件
  3. 運行:當就緒的線程被調度並獲得處理器資源時,便進入運行狀態, run()方法定義了線程的操作和功能
  4. 阻塞:在某種特殊情況下,被人爲掛起或執行輸入輸出操作時,讓出 CPU 並臨時中止自己的執行,進入阻塞狀態
  5. 死亡:線程完成了它的全部工作或線程被提前強制性地中止

在這裏插入圖片描述

四、線程的同步

  • 問題的提出
    多個線程執行的不確定性引起執行結果的不穩定
    多個線程對賬本的共享,會造成操作的不完整性,會破壞數據。

  • 問題的原因:
    當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數據的錯誤。

  • 解決辦法:
    對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。

  • 關於synchronized關鍵字,鎖住的東西是什麼?個人認爲它鎖住的是一個對象。

    1. 普通方法加Synchronized(同步鎖),鎖的當前方法對應的對象,當前的對象的所有加了同步鎖的方法是共用一個同步鎖。
    2. 靜態的方法加synchronized,對於所有的對象都是使用同一個一個鎖。
    3. 代碼塊synchronized(this),所有當前的對象的synchronized(this)同步的的代碼都是使用同一個鎖,在不同方法有synchronized(this)代碼塊,只要它們對象是同一個,那麼就是同一個鎖。
    4. synchronized(a),這個小括號中傳入不同的對象就是不同的鎖,若是一個對象就是一個同一個鎖。
  • 下面例子使用synchronized關鍵字實現線程線程同步

/**
*此代碼邏輯是兩個用戶同時向同一個賬戶取錢,本來餘額爲3000,
*兩個用戶都要取2000,若是不加鎖直接多線程取錢,必定會出現餘額爲-1000的情況
*所需需要加鎖,使線程同步。
*/
public class Test2 {
	public static void main(String[] args) {
		//定義賬戶對象
		Acount a = new Acount();
		Acount a1 = new Acount();
		
		//多線程對象
		User u_weixin = new User(a, 2000);
		User u_zhifubao = new User(a, 2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付寶");
		
		weixin.start();
		zhifubao.start();
	}
}

class Acount{
	public static int money = 3000;//全局變量,所有的操作共享這個變量
	
	/**
	 * 提款,判斷賬戶錢夠不夠
	 * 多線程調用這個方法,就有問題,線程共享資源時,一個線程在執行這個方法沒有完畢時,另一個線程又開始執行這個方法
	 * 解決思路:顯然一個線程整體執行完這個方法,另一個線程再執行
	 * 通過synchronized同步鎖來完成
	 * 可以直接在方法上加上synchronized關鍵字
	 * 在普通方法上加同步鎖synchronized,鎖的是整個對象,不是某一個方法
	 * 不同的對象就是不同的鎖,普通方法加synchronized,線程使用不同的此方法的對象,還有共享資源的問題
	 * 
	 * 普通方法加同步鎖,鎖的當前方法對應的對象,當前的對象的所有加了同步鎖的方法是共用一個同步鎖
	 * @param m
	 */
	public synchronized void drawing(int m){
		String name = Thread.currentThread().getName();
		
		if(money < m){
			System.out.println(name + "操作,賬戶金額不足:" + money);
		}else{
			System.out.println(name + "操作,賬戶原有金額:" + money);
			System.out.println(name + "操作,取款金額:" + m);
			
			System.out.println(name + "操作,取款操作:原金額" + money + " - " + "取款金額" + m);
			money = money - m;
			System.out.println(name + "操作,取款後的餘額:" + money);
		}
		
	}
	
	public synchronized void drawing1(int m){
		String name = Thread.currentThread().getName();
		
		if(money < m){
			System.out.println(name + "操作,賬戶金額不足:" + money);
		}else{
			System.out.println(name + "操作,賬戶原有金額:" + money);
			System.out.println(name + "操作,取款金額:" + m);
			
			System.out.println(name + "操作,取款操作:原金額" + money + " - " + "取款金額" + m);
			money = money - m;
			System.out.println(name + "操作,取款後的餘額:" + money);
		}
		
	}
	
	/**
	 * 靜態的方法加synchronized,對於所有的對象都是使用同一個一個鎖
	 * @param m
	 */
	public static synchronized void drawing2(int m){
		String name = Thread.currentThread().getName();
		
		if(money < m){
			System.out.println(name + "操作,賬戶金額不足:" + money);
		}else{
			System.out.println(name + "操作,賬戶原有金額:" + money);
			System.out.println(name + "操作,取款金額:" + m);
			
			System.out.println(name + "操作,取款操作:原金額" + money + " - " + "取款金額" + m);
			money = money - m;
			System.out.println(name + "操作,取款後的餘額:" + money);
		}
		
	}
	
	/**
	 * 對代碼塊加入同步鎖
	 * 代碼塊synchronized(this),所有當前的對象的synchronized(this)同步的的代碼都是使用同一個鎖
	 * @param m
	 */
	public void drawing3(int m){
		synchronized(this){//表示當前的對象的代碼塊被加了synchronized同步鎖
			//用this鎖代碼塊是代表當前的對象,如果在其他方法中也有synchronized(this)的代碼塊使用的都是同一個同步鎖
			String name = Thread.currentThread().getName();
			
			if(money < m){
				System.out.println(name + "操作,賬戶金額不足:" + money);
			}else{
				System.out.println(name + "操作,賬戶原有金額:" + money);
				System.out.println(name + "操作,取款金額:" + m);
				
				System.out.println(name + "操作,取款操作:原金額" + money + " - " + "取款金額" + m);
				money = money - m;
				System.out.println(name + "操作,取款後的餘額:" + money);
			}
		}
	}
	
	public void drawing4(int m){
		synchronized(this){//表示當前的對象的代碼塊被加了synchronized同步鎖
			//用this鎖代碼塊是代表當前的對象,如果在其他方法中也有synchronized(this)的代碼塊使用的都是同一個同步鎖
			String name = Thread.currentThread().getName();
			
			if(money < m){
				System.out.println(name + "操作,賬戶金額不足:" + money);
			}else{
				System.out.println(name + "操作,賬戶原有金額:" + money);
				System.out.println(name + "操作,取款金額:" + m);
				
				System.out.println(name + "操作,取款操作:原金額" + money + " - " + "取款金額" + m);
				money = money - m;
				System.out.println(name + "操作,取款後的餘額:" + money);
			}
		}
	}
	
	/**
	 * synchronized修飾代碼塊,想要根據不同的對象有不同的鎖
	 * synchronized(a),這個小括號中傳入不同的對象就是不同的鎖
	 * @param m
	 */
	public void drawing5(int m,Acount a){
		synchronized(a){//表示通過方法的參數傳遞進來的對象的代碼塊被加了synchronized同步鎖
			//不同的對象就有不同的同步鎖
			String name = Thread.currentThread().getName();
			
			//如果是微信操作的,先不執行,等支付寶操作,支付寶操作完,微信再繼續操作
			if(name.equals("微信")){
				try {
					a.wait();//當前的線程進入等待的阻塞狀態
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
			if(money < m){
				System.out.println(name + "操作,賬戶金額不足:" + money);
			}else{
				System.out.println(name + "操作,賬戶原有金額:" + money);
				System.out.println(name + "操作,取款金額:" + m);
				
				System.out.println(name + "操作,取款操作:原金額" + money + " - " + "取款金額" + m);
				money = money - m;
				System.out.println(name + "操作,取款後的餘額:" + money);
			}
			
			if(name.equals("支付寶")){
				try {
					a.notify();//喚醒當前優先級最高的線程,進入就緒狀態
//					a.notifyAll();//喚醒當前所有的線程,進入就緒狀態
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
} 

class User implements Runnable{
	Acount acount;
	int money;
	public User(Acount acount,int money){
		this.acount = acount;
		this.money = money;
	}
	@Override
	public void run() {
//		acount.drawing(money);
//		if(Thread.currentThread().getName().equals("微信")){
////			acount.drawing(money);
//			acount.drawing3(money);
//		}else{
////			acount.drawing1(money);
//			acount.drawing4(money);
//		}
//		acount.drawing2(money);//調用類的靜態方法
		
//		acount.drawing3(money);
		
		acount.drawing5(money, acount);
	}
	
}

五、線程的死鎖問題

  • 死鎖
    不同的線程分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。
  • 解決方法
    專門的算法、原則,比如加鎖順序一致
    儘量減少同步資源的定義,儘量避免鎖未釋放的場景

六、線程間通信

線程間通信不是說線程進行收發信息交流的意思,線程間通信是線程間有相應的邏輯,能讓各個線程能按照一定邏輯有序的執行,比如自己暫停讓別人先執行,等等。
線程間的通信運用的3個方法wait() 與 notify() 和 notifyAll()。

  1. wait():令當前線程掛起並放棄CPU、同步資源,使別的線程可訪問並修改共享資源,而當前線程排隊等候再次對資源的訪問
  2. notify():喚醒正在排隊等待同步資源的線程中優先級最高者結束等待
  3. notifyAll ():喚醒正在排隊等待資源的所有線程結束等待.
    Java.lang.Object提供的這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常,當然也可以不放在synchronized,不過要捕獲異常。以上代碼drawing5(int m,Acount a)中有體現。

七、經典線程同步—生產者/消費者問題


/**
 * 生產者與消費者
 * @author lby
 *
 */
public class Test3 {
	public static void main(String[] args) {
		Clerk c = new Clerk();
		//消費時不生產,生產時不消費
		
		//生產者
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (c) {
					while(true){//無限循環代表無限的生產次數
						if(c.productNum == 0){//產品數爲0,開始生產
							System.out.println("產品數爲0,開始生產");
							while(c.productNum < 4){
								c.productNum++;//增加產品
								System.out.println("庫存:" + c.productNum);
							}
							System.out.println("產品數爲" + c.productNum + ",結束生產");
							
							c.notify();//喚醒消費者線程
						}else{
							try {
								c.wait();//生產者線程等待
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
		}, "生產者").start();
		
		//消費者
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (c) {
					while(true){//無限循環代表無限的消費次數
						if(c.productNum == 4){//產品數爲4,開始消費
							System.out.println("產品數爲4,開始消費");
							while(c.productNum > 0){
								c.productNum--;//消費產品
								System.out.println("庫存:" + c.productNum);
							}
							System.out.println("產品數爲" + c.productNum + ",結束消費");
							
							c.notify();//喚醒生產者線程
						}else{
							try {
								c.wait();//消費者線程等待
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
		}, "消費者").start();
		
	}
}


class Clerk{
	public static int productNum = 0;
}

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