java多線程學習筆記(四)

一、線程簡介

請看:https://blog.csdn.net/qq_33157666/article/details/103949005

二、線程狀態

請看:https://blog.csdn.net/qq_33157666/article/details/103949045

三、線程同步

請看:https://blog.csdn.net/qq_33157666/article/details/103949120

四、死鎖

多個線程各自佔有一些共享資源,並且互相等待其他線程佔有的資源才能運行,而導致兩個或者多個線程都在等待對方釋放資源,都停止執行的情形。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能會發生“死鎖”的問題。

示例:

/**
 * 死鎖:多個線程互相抱着對方需要的資源,然後形成僵持
 * 
 *
 */
public class DeadLock {

	public static void main(String[] args) {
		Makeup g1=new Makeup(0, "灰姑娘");
		Makeup g2=new Makeup(1, "白雪公主");
		g1.start();
		g2.start();
	}
}

//口紅
class Lipstick{
	
}

//鏡子
class Mirror{
	
}


class Makeup extends Thread{
	//需要的資源只有一份,用static來保證只有一份
	static Lipstick lipstick=new Lipstick();
	static Mirror mirror=new Mirror();
	
	int choice;//選擇
	String girlName;//使用化妝品的人
	
	public Makeup(int choice,String girlName) {
		this.choice=choice;
		this.girlName=girlName;
	}
	
	@Override
	public void run() {
		//化妝
		try {
			makeup();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//化妝,互相持有對方的鎖,就是需要拿到對方的資源
	private void makeup() throws InterruptedException{
		if(choice==0){
			synchronized(lipstick){//獲得口紅的鎖
				System.out.println(this.girlName+"獲得口紅的鎖");
				Thread.sleep(1000);
				
				synchronized (mirror) {//一秒鐘後獲得鏡子的鎖,此時是在包住了對方鎖
					System.err.println(this.girlName+"獲得鏡子的鎖");
				}
			}
		}else{
			synchronized(mirror){//獲得鏡子的鎖
				System.out.println(this.girlName+"獲得鏡子的鎖");
				Thread.sleep(2000);
				
				synchronized (lipstick) {//一秒鐘後獲得口紅的鎖
					System.err.println(this.girlName+"獲得口紅的鎖");
				}
			}
		}
	}
}

運行結果:

可以看到運行結果是處於僵持的狀態,程序卡死。

解決辦法:

將上面代碼中的makeup方法改爲:

	//化妝,互相持有對方的鎖,就是需要拿到對方的資源
	private void makeup() throws InterruptedException{
		if(choice==0){
			synchronized(lipstick){//獲得口紅的鎖
				System.out.println(this.girlName+"獲得口紅的鎖");
				Thread.sleep(1000);
			}
			synchronized (mirror) {//一秒鐘後獲得鏡子的鎖,
				System.err.println(this.girlName+"獲得鏡子的鎖");
			}
		}else{
			synchronized(mirror){//獲得鏡子的鎖
				System.out.println(this.girlName+"獲得鏡子的鎖");
				Thread.sleep(2000);
			}
			synchronized (lipstick) {//一秒鐘後獲得口紅的鎖
				System.err.println(this.girlName+"獲得口紅的鎖");
			}
		}
	}

小結:

產生死鎖的四個必要條件:

  • 互斥條件:一個資源每次只能被一個進程使用。
  • 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
  • 不剝奪條件:進程已獲得的資源,在爲使用完之前,不能強行剝奪。
  • 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。

 

五、線程通信及線程池

5.1 線程通信

應用場景:生產者和消費者問題

假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中的產品取走消費、

如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走爲止;

如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品爲止。

java提供了幾個方法解決線程之間的通信爲題:

wait():表示線程一直等待,直到其他線程通知,與sleep不同,會釋放鎖

wait(long timeout):指定等待的毫秒數

notify():喚醒一個處於等待狀態的線程

notifyAll():喚醒同一個對象上所有調用wait()方法的線程,優先級較高的線程優先調度

方法一:利用緩衝區解決:管程法

管程法:
        生產者:負責生產數據的模塊(可能是方法、對象、線程、進程);
        消費者:負責處理數據的模塊(可能是方法、對象、線程、進程);
        緩衝區:消費者不能直接使用生產這的數據,他們之間有個"緩衝區";
        生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據。

類似於我們去肯德基買漢堡,如果有做好的漢堡,我們去了可以直接買,如果沒有需要等大廚做好,在買。
示例:

/**
 * 併發協作模型"生產者/消費者模式"--》管程法
 * 生產者:負責生產數據的模塊(可能是方法、對象、線程、進程)
 * 消費者:負責處理數據的模塊(可能是方法、對象、線程、進程)
 * 緩衝區:消費者不能直接使用生產這的數據,他們之間有個"緩衝區"
 * 生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據
 *
 */
public class TestPC {
	public static void main(String[] args) {
		SynContaniner contaniner=new SynContaniner();
		new Productor(contaniner).start();
		new Comsumer(contaniner).start();
	}
}

//生產者
class Productor extends Thread{
	SynContaniner container;
	public Productor(SynContaniner contaniner){
		this.container=contaniner;
	}
	//生產
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			container.push(new Chicken(i));
			System.out.println("生產了"+i+"只雞");
		}
	}
}

//消費者
class Comsumer extends Thread{
	SynContaniner container;
	public Comsumer(SynContaniner contaniner){
		this.container=contaniner;
	}
	//消費
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("消費了--》"+container.pop().id+"只雞");
		}
	}
}

//產品
class Chicken{
	int id;//產品編號
	public Chicken(int id){
		this.id=id;
	}
}

//緩衝區
class SynContaniner{
	//需要一個容器大小
	Chicken[] chickens=new Chicken[10];
	//容器計數器
	int count=0;
	
	//生產者放入產品
	public synchronized void push(Chicken chicken){
		//如果容器滿了,就需要等待消費者消費
		//System.out.println("生產者:"+count+"---"+chickens.length);
		if(count==chickens.length){
			//通知消費者消費,生產者等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		//如果沒有滿,我們就需要丟入產品
		chickens[count]=chicken;
		count++;
		
		//可以通知消費者消費了
		this.notifyAll();
	}
	
	//消費者消費產品
	public synchronized Chicken pop(){
		//System.out.println("消費者:"+count);
		//判斷能否消費
		//System.out.println("count:----"+count);
		if(count==0){
			//等待生產者生產,消費者等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		//如果可以消費
		count--;
		Chicken chicken=chickens[count];
		
		//吃完了,通知生產者生產
		this.notifyAll();
		return chicken;
	}
}

方法二:信號燈法,標誌位解決

設置一個標誌位,如果標誌位爲true,則生產者生產,生產完把標誌位置爲false,否則生產者等待;

標誌位爲false,消費者消費,消費完把標誌位置爲true,否則等待。

示例:

//測試生產者消費者問題2:信號燈法,標誌位解決
public class TestPC2 {
	public static void main(String[] args) {
		Tv tv=new Tv();
		new Player(tv).start();
		new Watcher(tv).start();
	}
	
}


//生產者-->演員
class Player extends Thread{
	Tv tv;
	public Player(Tv tv){
		this.tv=tv;
	}
	
	@Override
	public void run() {
		for(int i=0;i<20;i++){
			if(i%2==0){
				this.tv.play("快樂大本營播放中");
			}else{
				this.tv.play("抖音記錄美好生活");
			}
		}
	}
}

//消費者->觀衆
class Watcher extends Thread{
	Tv tv;
	public Watcher(Tv tv){
		this.tv=tv;
	}
	
	@Override
	public void run() {
		for(int i=0;i<20;i++){
			tv.watch();
		}
	}
}

//產品->節目
class Tv{
	//演員表演,觀衆等待
	//觀衆觀看,演員等待
	String voice;//演員的節目
	boolean flag=true;
	
	//表演
	public synchronized void play(String voice){
		if(!flag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("演員表演了:"+voice);
		//通知觀衆觀看
		this.notifyAll();//通知
		this.voice=voice;
		this.flag=!this.flag;
	}
	
	//觀看
	public synchronized void watch(){
		if(flag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("觀看了:"+voice);
		//通知演員表演
		this.notifyAll();
		this.flag=!this.flag;
	}
}

5.2 線程池

背景:經常創建和銷燬線程,使用量特別大的資源,比如併發情況下的線程,對性能影響很大。

思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷燬、實現重複利用。類似生活中交通工具。

好處

提高響應速度(減少了創建新線程的時間)

降低資源消耗(重複利用線程池中的線程,不需要每次都創建)

便於線程管理:corePoolSize:線程池的大小

                          maximumPoolSize:最大線程數

                          keepAliveTime:線程沒有任務時最多保持多長時間會終止

示例

/**
 * 測試線程池
 *
 */
public class TestPool {
	
	public static void main(String[] args) {
		//1.創建服務,創建線程池
		//newFixedThreadPool 參數爲:線程池大小
		ExecutorService service=Executors.newFixedThreadPool(10);
		
		//執行
		service.execute(new MyThread());
		service.execute(new MyThread());
		service.execute(new MyThread());
		service.execute(new MyThread());
		service.execute(new MyThread());
		
	}
}

class MyThread implements Runnable{
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
		
	}
}
發佈了58 篇原創文章 · 獲贊 87 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章