【學習筆記】JAVA多線程理解

內容:Java黑馬就業班 P307-334


java程序屬於搶佔式調度,那個線程的優先級高,那個線程優先執行:同一個優先級,隨機選擇一個執行。
每個新線程都會開闢一個新的棧空間來執行run方法,cpu可以選擇線程執行。

創建多線程程序的方式

創建多線程程序的第一種方式

創建Thread類的子類
java.lang.Thread類是描述線程的類,想要實現多線程程序,就必須繼承Thread類

實現步驟:
1.創建一個Thread類的子類

2.在Thread類的子類種重寫Thread類中的run方法,設置線程任務(開啓線程要做什麼)

//這裏創建了一個叫MyThread的子類
public class MyThread extends Thread{
	@Override
	public void run(){
		......
	}
}

3.創建Thread類中的子類對象

4.調用Thread類中的方法,開始新的線程,執行run方法

public class Main{
	public static void main(String []args){
		MyThread mt=new MyThread();
		mt.start();
	}
}

void start()使該線程開始執行;Java虛擬機調用該線程的run方法。

結果是兩個線程併發地運行;當前線程和另一個線程。

多次啓動一個線程是非法的,特別是當線程已經結束執行後,不能再重新啓動。

創建多線程程序的第二種方式

實現Runnable接口
java.lang.Runnable,Runnable接口應該由那些打算通過某一線程執行其實例的類來實現。類必須定義一個稱爲run的無參數方法
java.lang.Thread類的構造方法
Thread(Runnable target)分配新的Thread對象。
Thread(Runnable target,String name)分配新的Thread對象。

實現步驟:
1.創建一個Runnable接口的實現類

2.在實現類中重寫 RunnabLe接口的run方法,設置線程

public class RunnableImp1 implements Runnable{
	@Override
	public void run(){
		......
	}
}

3.創建一個 Runnable接口的實現類對象

4.創建 Thread類對象,構造方法中傳遞Runnable接口的實現類對象

5.調用 Thread類中的 start方法,開啓新的線程執行run方法

public class Main{
	public static void main(String []args){
		RunnableImp1 run=new RunnableImp1();
		Thread t=new Thread(run);
		t.start();
	}
}

兩種方式的區別

實現了Runnable接口創建多線程程序的好處:

1.避免了單繼承的侷限性

(1)一個類只能繼承一個類(一個人只能有一個親爹)類繼承了 Thread類就不能繼承其他的類
(2)實現了Runnable接口,還可以繼承其他的類,實現其他的接口

2.增強了程序的擴展性,降低了程序的耦合性(解耦)

(1)實現 Runnable接口的方式,把設置線程任務和開啓新線程進行了分離(解耦)
(2)實現類中,重寫了run方法:用來設置線程任務
(3)創建Thread類對象,調用 start方法:用來開啓新線程

使用匿名內部類實現線程的創建

匿名:沒有名字
內部類:寫在其他類內部的類

匿名內類作用:簡化代碼
把子類繼承父類,重寫父類的方法,創建子類對象合一步完成
把實現類實現類接口,重寫接口中的方法,創建實現類對象合成一步完成

匿名內部類的最終產物:子類/實現類對象,而這個類沒有名字

//格式
	new 父類/接口(){
		重複父類/接口中的方法
	};
public class Main{
	public static void main(String []args){
		//第一種 線程父類是Thread
		new Thread(){
			@Override
			public void run(){
				......
			}
		}.start();
	
		//第二種 線程接口Runnable
		Runnable r=new Runnable(){
			@Override
			public void run(){
				......
			}
		};
		new Thread(r).start();

		//第三種 在第二種的基礎上繼續匿名
		new Thread(new Runnable(){
			@Override
			public void run(){
				......
			}
		}).start();
	}
}

線程同步機制

一、同步代碼塊

同步代碼塊:
synchronized可以用於方法鐘的某個區塊中,表示只對這個區塊的資源實行互斥訪問。

synchronized(同步鎖){
	需要同步操作的代碼
}

同步鎖:
對象的同步鎖只是一個概念,可以想象爲在對象上標記了一個鎖
1.鎖對象 可以是任意類型
2.多個線程對象 要使用同一把鎖

注意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程只能在外等着(BLOCKED)。

注意:

1.通過代碼塊中的鎖對象,可以使用任意的對象
2.但是必須保證多個線程使用的鎖對象是同一個
3.鎖對象作用:把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行

二、同步方法

同步方法:
使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等着。

public synchronized void method(){
	可能會產生線程安全問題的代碼
}

同步鎖是誰?
對於非 static方法同步鎖就是this。
對於 static方法我們使用當前方法所在類的字節碼對象(類名.class)

使用步驟:
1.把訪問了共享數據的代碼抽取出來,放到一個方法中
2.在方法上添加 synchronized修飾符

三、Lock鎖

java.util.concurrent.locks.Lock機制提供了比synchronized代碼塊和 synchronized方法更廣泛的鎖定操作。同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。

Lock接口中的方法:

void lock() 獲取鎖
void unlock() 釋放鎖

java.util.concurrent.locks.ReentrantLock implements lock 接口

使用步驟:
1.在成員位置創建一個 ReentrantLock對象

Lock l=new ReentrantLock ();

2.在可能會出現安全問題的代碼前調用Lock接口中的方法Lock獲取鎖

l.lock();

在可能會出現安全問題的代碼後調用Loc接口中的方法 unLock釋放鎖

l.unlock();

線程狀態

線程概述圖
在這裏插入圖片描述
Timed Waiting(計時等待)
當我們調用了sleep方法之後,當前執行的線程就進入到”休眠狀態“,其實就是所謂的 Timed Waiting

在這裏插入圖片描述
Timed blocked(鎖阻塞)
沒爭取到鎖對象就是鎖阻塞狀態
在這裏插入圖片描述
WAITING(無限等待狀態)
直接上demo

package demo1;

public class demo1wait {
	/*
	 * 等待喚醒案例:線程之間的通信
	 * 創建一個顧客線程(消費者):告知老闆要的包子的種類和數量,調用wait方法,放棄cpu的執行,進入到WAITING狀態
	 * 創建一個老闆線程(生產者):花了5秒做包子,做好包子之後,調用notify方法,喚醒顧客吃包子
	 * 
	 * 注意:
	 * 		顧客和老闆線程必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行
	 * 		同步使用的鎖對象必須保證唯一
	 * 		只有鎖對象才能調用wait方法和notify方法
	*/
	public static void main(String[] args) {
		//創建鎖對象,保證唯一
		final Object obj=new Object();
		//創建一個顧客線程(消費者)
		new Thread(){
			@Override
			public void run(){
				//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
				synchronized(obj){
					System.out.println("我要兩個菜包!");
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("你家的菜包子真好吃");
				}
			}
		}.start();
		//創建一個老闆線程(生產者)
		new Thread(){
			@Override
			public void run(){
				try {
					sleep(5000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
				synchronized(obj){
					System.out.println("老闆:您要的兩個菜包子來啦!");
					obj.notify();
				}
			}
		}.start();
	}
}

如果此時有兩個顧客同時來買包子,老闆應該可以一起做,一起給,那就不能用notify方法了,用notifyAll方法。上demo

package demo1;

public class demo1wait {
	/*
	 * 等待喚醒案例:線程之間的通信
	 * 創建一個顧客線程(消費者):告知老闆要的包子的種類和數量,調用wait方法,放棄cpu的執行,進入到WAITING狀態
	 * 創建一個老闆線程(生產者):花了5秒做包子,做好包子之後,調用notify方法,喚醒顧客吃包子
	 * 
	 * 注意:
	 * 		顧客和老闆線程必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行
	 * 		同步使用的鎖對象必須保證唯一
	 * 		只有鎖對象才能調用wait方法和notify方法
	*/
	public static void main(String[] args) {
		//創建鎖對象,保證唯一
		final Object obj=new Object();
		//創建一個顧客線程(消費者)
		new Thread(){
			@Override
			public void run(){
				while(true){
					synchronized(obj){
						System.out.println("顧客1:我要兩個菜包!");
						try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						System.out.println("顧客1:你家的菜包子真好吃");
						System.out.println("--------------------------");
					}
				}
				//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
			}
		}.start();
		new Thread(){
			@Override
			public void run(){
				while(true){
					synchronized(obj){
						System.out.println("顧客2:我要兩個菜包!");
						try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						System.out.println("顧客2:你家的菜包子真好吃");
						System.out.println("--------------------------");
					}
				}
				//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
			}
		}.start();
		//創建一個老闆線程(生產者)
		new Thread(){
			@Override
			public void run(){
				while(true){
					try {
						sleep(5000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//保證等待和喚醒的線程只能有一個執行,需要使用同步技術
					synchronized(obj){
						System.out.println("老闆:您要的兩個菜包子來啦!");
//						obj.notify();
						obj.notifyAll();
					}
				}
			}
		}.start();
	}
}

線程池

一、線程池概述

線程池是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

在JDK1.5之後,JDK內置了線程池,可直接使用

線程池是個容器,那就是用集合來裝的(ArrayList,HashSet,LinkedList,HashMap)

當程序第一次啓動的時候創建多個線程保存到一個集合中;當我們想要使用線程的時候就可以從集合中取出來線程使用。
在這裏插入圖片描述

Thread t=list.remove();//返回被移除的元素,線程只能被一個任務使用
//以LinkedList爲例
Thread t=linked.removeFirst();

當我們使用完畢線程,需要把線程歸還給線程池

list.add(t);
//以LinkedList爲例
linked.addLast(t);

在這裏插入圖片描述
合理利用線程池的好處

1.降低資源消耗。減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個。任務

2.提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。

3.提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

二、線程池的使用

java.util.concrrent.Executors:線程池的工廠類,用來生成線程池
Executors類中的靜態方法:
static ExecutorService newFixedThreadPool(int nThreads) 創建一個可重用固定線程數的線程池
參數:
int nThreads:創建線程池中包含的線程數量
返回值:
ExecutorService接口,返回的是ExecutorService接口的實現類對象,我們可以使用ExecutorService接口接收(面向接口編程)

java.util.concurrent.ExecutorService:線程池接口
用來從線程池中獲取線程,調用start方法,執行線程任務
submit(Runnable task)提交一個Runnable任務用於執行
關閉/銷燬線程池的方法
void shutdown()

線程池的使用步驟:
1.使用線程池的工廠類Executors裏邊提供的靜態方法 newFixedThreadpool生產一個指定線程數量的線程池
2.創建一個類,實現 Runnable接口,重寫run方法,設置線程任務
3.調用ExecutorService中的方法 submit,傳遞線程任務(實現類),開啓線程,執行run方法調用
4.ExecutonService中的方法 shutdown銷燬線程池(不建議執行)

demo
Runnable接口實現類

package demo1;

public class RunnableImp implements Runnable{

	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"正在運行");
	}

}

主函數類

package demo1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo1wait {
	public static void main(String[] args) {
		//開啓3個線程的線程池
		ExecutorService es=Executors.newFixedThreadPool(3);
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		//es.shutdown();
	}
}

運行結果
在這裏插入圖片描述

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