JavaDay24 多線程與多進程


tags:

  • 進程
  • 線程

JavaDay24 多線程與多進程

@toc

代碼示例:

package DemoDay24;

import org.junit.jupiter.api.Test;

/**使用線程實現同時視頻和語音
 * @author GJXAIOU
 * @create 2019-07-24-20:54
 */

class VideoThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("視頻中。。。。。");
        }
       
    }
}
class AudioThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("語音中。。。。。");
        }
        
    }
}

public class Demo1 {
    
    //方法一:使用main函數進行調試
//    public static void main(String[] args) {
//        VideoThread videoThread = new VideoThread();
//        AudioThread audioThread = new AudioThread();
//
//        videoThread.start();
//        audioThread.start();
//    }

    //方法二:使用JUnit中@test進行調試
    @Test
    public void test(){
        VideoThread videoThread = new VideoThread();
        AudioThread audioThread = new AudioThread();

        videoThread.start();
        audioThread.start();
    }

}

程序運行結果:
每次運行結果會不同,進程會別搶佔;

語音中。。。。。
語音中。。。。。
語音中。。。。。
語音中。。。。。
語音中。。。。。
視頻中。。。。。
視頻中。。。。。
視頻中。。。。。
視頻中。。。。。
視頻中。。。。。

一、線程中的常用方法

方法名 含義 說明
Thread(String name); 初始化線程的名字 屬於線程的一個有參數的構造方法
setName(String name); 修改線程的名字
getName(); 獲取線程的名字
sleep(); static靜態方法,通過Thread類名調用,這裏需要處理一些異常,要求當前線程睡覺多少毫秒; 【哪一個線程執行了sleep方法,哪一個線程就睡覺】。
currentThead(); static靜態方法,返回當前的線程對象; 【哪一個線程執行了currentThread方法,就返回哪一個線程對象】。
getPriority(); 返回當前線程的優先級 CPU執行的優先級,不是絕對的,僅僅是提升概率。
setPriority(int newPriority); 設置線程的優先級。
  • 【注意】
    線程的優先級範圍是從1 ~ 10, 10最高,1最低
    這裏的優先級只是提高了當前線程擁有CPU執行權的概率,並不能完全保證當前線程能夠一定會佔用更多的CPU時間片。線程的默認優先級爲5。

    Thread[main,5,main]
    Thread[Thread-0,5,main]
    Thread[線程名, 優先級, 線程組名]

  • 線程中常見方法的測試:
    run()方法中不能拋出異常,只能使用 try-catch

package DemoDay24;

/**
 * @author GJXAIOU
 * @create 2019-07-24-21:23
 */
public class Demo2 extends Thread {

    public Demo2(String name) {
        super(name);//調用父類Thread的有參構造方法
    }

    
    @Override
    public void run() {
        //這裏是Demo2線程對象的線程代碼
        System.out.println("28:" + Thread.currentThread());

        for (int i = 0; i < 5; i++) {
            System.out.println("自定義線程");
		/*
		 在其他方法中, 使用sleep方法,可以拋出,可以捕獲,
		 但是在run方法爲什麼只有捕獲沒有拋出?因爲這是一個語法規則:
		 在Java中,重寫父類的方法,要求和父類的方法聲明一模一樣,
		 在Thread類中,run方法沒有拋出異常,所以在子類中,你也不能拋出異常,要和父類一致
		 */
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //這裏是main線程
        Demo2 d = new Demo2("狗蛋");

        d.setName("狗娃");
        d.setPriority(10);
        d.start(); //開啓自定義線程,執行自定義線程中的run方法裏面的功能
        System.out.println("39:" + Thread.currentThread());

        for (int i = 0; i < 5; i++) {
            System.out.println("這裏是main線程");
            sleep(100);
        }
    }
}


程序運行結果:

39:Thread[main,5,main]
這裏是main線程
28:Thread[狗娃,10,main]
自定義線程
這裏是main線程
自定義線程
自定義線程
這裏是main線程
這裏是main線程
自定義線程
自定義線程
這裏是main線程

下面的 InterfaceA 和 testA 是爲了測試什麼時候是不能使用拋出異常,只能使用 try-catch

package com.qfedu.a_thread;

interface A {
	public void testA();
}

public class Demo2 extends Thread implements A{
	public Demo2(String name) {
		super(name);//調用父類Thread的有參構造方法
	}
	
	@Override
	public void testA() {
		//這裏也無法拋出異常,兩種處理方法,第一種,捕獲異常,
		//第二種,在接口中聲明方法部分,聲明該異常
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
	}
//--------------------------------------------------	
//下面代碼省略
}


二、線程的生命週期

[外鏈圖片轉存失敗(img-j6ZAToze-1568986092781)($resource/%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.png)]


三、線程的共享資源問題

以動物園買票爲示例:
一共有 50張票 3個窗口同時售賣
這裏隱含多線程,這裏可以把3個窗口看做是3個線程

  • 第一次問題:
    發現票每一張都被買了三次

    • 原因:
      因爲Ticket是在每一個線程中run方法裏面的一個局部變量,這個局部變量是每一個線程對象都擁有的,這裏Ticket就是不在是一個共享資源
    • 處理方式:
      把Ticket變成成員變量
  • 第二次問題:
    發現貌似每一張票還都是賣了50次,而且這裏還優化了售賣的算法

    • 原因:
      這裏Ticket變成了一個成員變量,在每一個線程對象中,都擁有這個Ticket成員變量,每一個成員變量是一個獨立的個體,不是共享資源
    • 處理方式:
      用static修飾ticket成員變量,變成一個存放在數據共享區的一個靜態成員變量
  • 第三次問題:
    發現會出現幾張票是買了多次的

    • 原因:
      因爲窗口1在賣票的時候,還沒有運行到ticket–這條語句的時候,下一個窗口2開始執行賣票算法
      這裏窗口2賣的票是窗口1還沒有ticket–的票
      [外鏈圖片轉存失敗(img-ArlJjEsh-1568986092782)($resource/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98.png)]
      處理方式:
      上鎖,鎖門
  • Java中的線程同步機制
    方式1:
    同步代碼塊:

synchronized (鎖對象) {
  //需要同步的代碼;
}

同步代碼塊的注意事項:

  1. 鎖對象,可以是任意的一個對象, 但是必須是同一個對象!!!不能在這裏使用new 來創建匿名對象
  2. sleep() 不會釋放鎖對象,不會開鎖。例如: 廁所有人關門睡着了
  3. 使用synchronized 同步代碼塊的時候,必須是真正意義上存在共享資源的線程問題,纔會使用
    而且通常情況下,用synchronized鎖住的代碼越少越好,提高代碼執行效率
package com.qfedu.a_thread;


class SaleTicket extends Thread {
  private static int ticket = 50;

	public SaleTicket(String name) {
		super(name);
	}

	@Override
	public void run() {

		while (true) {
			synchronized ("你好") { 
			//可以使用"你好 "的任何確定的對象 
			//不能使用new Demo3()創建不同對象,即對應不同的鎖 
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName()+
					":賣出來第" + ticket+ "張票");
					try {
						sleep(500); //睡眠也不會釋放鎖對象
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {
					System.out.println("賣完了");
					break;
				}

				ticket--;
			}
		}
	}
}

public class Demo3 {
	public static void main(String[] args) {
		SaleTicket s1 = new SaleTicket("窗口1");
		SaleTicket s2 = new SaleTicket("窗口2");
		SaleTicket s3 = new SaleTicket("窗口3");

		s2.start();
		s1.start();
		s3.start();

	}
}

程序運行結果:

窗口1:賣出來第50張票
窗口1:賣出來第49張票
。。。。。
窗口3:賣出來第28張票
窗口3:賣出來第27張票
窗口3:賣出來第26張票
窗口3:賣出來第2張票
窗口3:賣出來第1張票
賣完了
賣完了
賣完了

(一)死鎖

  • 出現死鎖的原因:
    1.存在兩個或兩個以上的共享資源
    2.存在兩個或者兩個以上的線程使用這些共享資源
    下面的代碼會出現死鎖;
package com.qfedu.a_thread;
class DeadLock extends Thread {
	public DeadLock(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		if (Thread.currentThread().getName().equals("小胖")) {
			synchronized ("電池") {
				System.out.println("小胖有電池,想要遙控器");
				
				synchronized ("遙控器") {
					System.out.println("小胖拿到了遙控器,打開了投影儀");
				}
			}
		} else if (Thread.currentThread().getName().equals("逗比")) {
			synchronized ("遙控器") {
				System.out.println("逗比有遙控器,想要電池");
				
				synchronized ("電池") {
					System.out.println("逗比拿到了電池,打開了投影儀");
				}
			}
		}		
	}
}

public class Demo4 {
	public static void main(String[] args) {
		DeadLock d1 = new DeadLock("小胖");
		DeadLock d2 = new DeadLock("逗比");
		
		d1.start();
		d2.start();		
	}
}

輸出結果:

小胖有電池,想要遙控器
逗比有遙控器,想要電池

(二)自定義線程

Java語言是一種單繼承,多實現【遵從】面向對象的語言

  • 自定義線程的方式:
    • 方式1:
      1.自定義一個類,繼承Thread類
      2.重寫Thread裏面的run方法,把線程的功能代碼放入到run方法中
      3.創建自定義線程類對象
      4.開啓線程,使用start方法

      • 弊端:
        因爲Java是一個單繼承的語言,一旦某一個類繼承了Thread類,就無法再繼承其他類,或者說一個類繼承了其他類,也就沒有辦法繼承Thread類
    • 方式2: 強烈推薦
      【遵從】Runnable接口實現自定線程類
      1.自定義一個類,【遵從】Runnable接口
      2.實現Runnable接口中唯一要求的方法 Run方法,把線程的功能代碼寫入到run方法中
      3.創建Thread類對象,並且把【遵從】Runnable接口的自定義類對象,作爲參數傳入到Thread構造方法中
      4.調用Thread類對象的start方法,開啓線程

package com.qfedu.a_thread;
import java.util.Arrays;
import java.util.Comparator;

class TestRunnable implements Runnable {

	//實現自定義線程類,遵從Runnable接口要求實現的Run方法,把線程代碼寫入到Runnable裏面
	@Override
	public void run() {
		for (int i = 0 ; i < 10; i++) {
			System.out.println("當前線程爲:" + Thread.currentThread());
		}
	}	
}

public class Demo5 {
	public static <T> void main(String[] args) {
		//創建Thread類對象,調用Thread構造方法中,需要傳入Runnable接口實現類對象的方法~
		//方法一:
		Thread t1 = new Thread(new TestRunnable());//匿名對象

		//方法二:
		Thread t2 = new Thread(new Runnable() { //匿名內部類的匿名對象,不再需要定義上面的TestRunnable類 
			
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					System.out.println("匿名內部類的匿名對象,作爲方法的參數,這裏是作爲線程對象的參數" + 
							Thread.currentThread());
				}
			}
		});
		
		t1.start();
		t2.start();
		
		/*
		target是在創建Thread類對象時候,傳入的【遵從】Runnable接口的實現類,這個實現類中
		實現類【遵從】Runnable接口要求實現的run方法,在run方法中,就是定義的線程代碼
		在Thread類中有一個成員變量
		//What will be run
		private Runnable target; 
		
		@Override
		public void run() {
			if (target != null) {
				target.run();
			}
		}
		*/
	}
}

三、守護線程(後臺線程)

例如:
軟件的Log日誌文件,軟件的自動更新,軟件的自動下載

特徵:
如果整個程序再運行過程中,只剩下一個守護線程,那麼這個守護線程也就沒有意義了,會自動停止

JVM的垃圾回收機制是守護線程。

這裏當主線程停止,則下載也會自動停止;

package com.qfedu.a_thread;

public class Demo6 extends Thread {
	
	public Demo6(String name) {
		super(name);
	}
	
	//模擬後臺下載更新的線程
	@Override
	public void run() {
		for (int i = 0; i <= 100; i++) {
			System.out.println("軟件更新下載中………………" + i + "%");
			
			if (i == 100) {
				System.out.println("軟件更新下載完成,是否安裝~~");
			}
			
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Demo6 d = new Demo6("後臺線程");
		
		//設置當前線程爲守護線程或者是後臺線程
		d.setDaemon(true);//true爲守護線程
		
		//System.out.println(d.isDaemon());
		d.start();
		for (int i = 0; i <= 50; i++) {
			Thread.sleep(10);
			System.out.println("主線程:" + i);
		}
	}
}

四、線程通訊

  • 線程通訊:
    一個線程完成任務之後,通知另一個線程來完成該線程應該執行的任務

生產者和消費者問題:這裏商品是兩個線程直接的共享資源

wait(); 等待,如果一個線程執行了wait方法,那麼這個線程就會進入臨時阻塞狀態,等待喚醒,這個喚醒必須其他線程調用notify() 方法喚醒;
notify(); 喚醒,喚醒線程池中進入【臨時阻塞狀態】的一個線程

  • 注意事項:
  1. wait()和notify()這兩個方法都是Object類的方法
  2. 在消費者生產者模式下,鎖對象只能是商品;
package com.qfedu.a_thread;

//兩者之間的共享資源
class Product {
	String name; //商品的名字
	int price; //價格
	
	boolean flag = false; //產品是否生產成功,如果成功flag設置爲true,消費者購買之後,設置爲false
}

class Producer extends Thread {
	Product p;  //商品的類對象,是和消費者之間的共享資源
	
	public Producer(Product p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		int count = 0;
		while (true) {
			synchronized (p) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (!p.flag) { //p.flag == false
					
					//商品不存在,生產過程
					if (count % 2 == 0) {
						p.name = "紅辣椒擀麪皮";
						p.price = 5;
					} else {
						p.name = "唐風閣肉夾饃";
						p.price = 10;
					}
					
					count++;
					System.out.println("生產者生產了:" + p.name + ":" + p.price);
					p.flag = true;
					//生產結束,喚醒消費者
					p.notify();
				} else {
					//商品存在,要求消費者來購買,生產者進入臨時阻塞
					try {
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} // try - catch
				}// if - else
			} // 同步代碼塊
		}// while (true)
	} //run()
}

class Customer extends Thread {
	Product p; //商品類對象,是和生產者之間的共享資源
	
	public Customer(Product p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (p) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if (p.flag) { //p.flag == true 
					//這裏表示商品存在
					System.out.println("消費者購買了:" + p.name + ":" + p.price);
					
					p.flag = false; //表示消費者購買完畢,要求生產者生產
					//需要喚醒生產者
					p.notify(); //打開線程鎖
				} else {
					try {
						//商品不存在,消費者進入臨時阻塞
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} // try - catch
				}// if - else 
			} //同步代碼塊
		} // while (true)
	} //run()
}

public class Demo7 {
	public static void main(String[] args) {
		Product product = new Product();
		
		Producer p = new Producer(product);
		Customer c = new Customer(product);
		
		p.start();
		c.start();
	}
}

[外鏈圖片轉存失敗(img-c1SINa4C-1568986092783)($resource/%E5%8F%AF%E9%81%87%E4%B8%8D%E5%8F%AF%E6%B1%82%E7%9A%84%E9%94%99%E8%AF%AF.png)]

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