JAVA線程


JAVA線程

線程

串行和並發

進程之間資源不共享,所以在程序中一般不單獨開辟進程

線程是一個任務執行的最小單元

線程的並發和進程是一樣的,也是CPU通過中斷進行“假並發”

多個線程同時訪問的資源叫臨界資源


線程的狀態

題外話:時間片

時間片(timeslice)又稱為“量子(quantum)”或“處理器片(processor slice)”是分時操作系統分配給每個正在運行的進程微觀上的一段CPU時間(在搶占內核中是:從進程開始運行直到被搶占的時間)。時間片通常很短(在Linux上為5ms-800ms)

時間片由操作系統內核的調度程序分配給每個進程。首先,內核會給每個進程分配相等的初始時間片(在Linux系統中,初始時間片也不相等,而是各自父進程的一半),然后每個進程輪番地執行相應的時間,當所有進程都處於時間片耗盡的狀態時,內核會重新為每個進程計算並分配時間片,如此往復。

系統通過測量進程處於“睡眠”和“正在運行”狀態的時間長短來計算每個進程的交互性,交互性和每個進程預設的靜態優先級Nice值)的疊加即是動態優先級,動態優先級按比例縮放就是要分配給那個進程時間片的長短。一般地,為了獲得較快的響應速度,交互性強的進程(即趨向於IO消耗型)被分配到的時間片要長於交互性弱的(趨向於處理器消耗型)進程。

搶占式多任務處理(Preemption)是計算機操作系統中,一種實現多任務處理(multi task)的方式,相對於協作式多任務處理而言。協作式環境下,下一個進程被調度的前提是當前進程主動放棄時間片;搶占式環境下,操作系統完全決定進程調度方案,操作系統可以剝奪耗時長的進程的時間片,提供給其它進程。

  • 每個任務賦予唯一的一個優先級(有些操作系統可以動態地改變任務的優先級);
  • 假如有幾個任務同時處於就緒狀態,優先級最高的那個將被運行;
  • 只要有一個優先級更高的任務就緒,它就可以中斷當前優先級較低的任務的執行;

1568035912437
1568036274048

線程的概念

一個線程就是一個程序內部的順序控制流

線程和進程的區別

1568032609559
1568032651986
java.lang.Thread模擬CPU實現線程,所要執行的代碼和處理的數據要傳遞給Thread類。

1568032763234

構造線程的方法:線程實例化的兩種方式

  • 法一:線程實例化
  1. 繼承Thread類,做一個線程子類(自定義的線程類)

  2. 重寫run方法,將需要並發執行的任務寫到run中

  3. 線程開辟需要調用start方法,使線程啟動,來執行run中的邏輯(如果直接調用run方法,這不會開辟新線程,或者線程不會進入就緒態,沒有實現多線程)

    多線程的效果:

    1568037231469
    主線程先結束,再執行邏輯

    這種方式可讀性更強

    缺點:由於JAVA的單繼承,則該類無法再繼承其他類,會對原有的繼承結構造成影響

  • 法二:通過Runnable接口

    1568037356734
    1568037439950
    用Runnable”聲明“了對象之后,要真正的實例化Thread對象,即Thread t2=new Thread(r1);這個語句

    不影響原有繼承關系

    缺點:可讀性較差


線程的常用操作

  1. 線程的命名

    1. 法一:使用setName命名

      1568040448103

    2. 法二:在構造方法中直接命名

      1568040497545

    3. 自定義一個類,繼承Thread,添加一個包括名字參數的構造方法

    1568079671069

  2. 線程的休眠

    線程休眠方法:

    Thread.sleep();//參數是以毫秒為單位的時間差

    1568079865226
    休眠會拋出一個InterruptedException異常,我們需要捕獲它

    線程休眠可以使一個線程由運行狀態變成阻塞態,休眠時間結束后回到就緒態,如果成功獲得時間片就變成運行狀態


線程的優先級

設置線程的優先級,只是修改這個線程可以去搶到CPU時間片的概率,並不是說優先級高的線程就一定能搶到CPU時間片

? 優先級是一個0-10的整數,默認是5

? 設置優先級必須放到這個線程開始執行(即Start)之前

? 題外話:Thread.currentThread().getName();//獲取當前線程

? 線程之間都是交替執行的,並不是優先級高的先執行完再執行優先級低的

1568081094353
在主方法中:

1568081122773

線程的禮讓(Yield)


禮讓:由執行到就緒態,即讓當前運行狀態的線程釋放自己的CPU資源

注意,放棄當前CPU時間片后,該線程和其他線程一起爭搶時間片,依然有可能搶到,所以禮讓的結果不一定是運行其他線程

yield是一個靜態方法,直接Thread.yield();就可

示例:

1568081705417
1568081759667

注意看,無論是線程1還是線程2,執行到3時都進行了禮讓,讓另一個線程執行(這是比較極端的狀態)

線程中的臨界資源問題


被多個線程要同時訪問的資源是臨界資源

多個線程同時訪問一個臨界資源會出現臨界資源問題,即臨界資源調用出錯

錯誤示例:

1568082371677
1568082336910
執行結果:

1568082388365
可以看到,這里對於restCount的調用是有問題的

錯誤原因

每個線程在字符串拼接、輸出之前時間片就被搶走,另一個線程訪問的是原來的線程做減法之后的restCount,最后誰先輸出不一定。

1568082814212
我們可以看到,這種差異是非常明顯的:當線程3做完減法變成0時,線程2還停留在78上,卡的時間可夠長的

解決臨界資源問題

不讓多個線程同時訪問--引入鎖的概念,為臨界資源加鎖。當其他線程訪問、看到有鎖時,就知道有其他線程正在訪問該資源,要等待該線程訪問完畢(解鎖)再訪問

? 常用的鎖:

1. 同步代碼段

2. 同步方法
3. 同步鎖

同步代碼段

在Java程序運行時環境中,JVM需要對兩類線程共享的數據進行協調:
1)保存在堆中的實例變量
2)保存在方法區中的類變量

這兩類數據是被所有線程共享的。

(程序不需要協調保存在Java 棧當中的數據。因為這些數據是屬於擁有該棧的線程所私有的。)

再復習一下堆棧:

方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。

棧:在Java中,JVM中的棧記錄了線程的方法調用。每個線程擁有一個棧在某個線程的運行過程中,如果有新的方法調用,那么該線程對應的棧就會增加一個存儲單元,即幀(frame)[幀也就是你學過的活動記錄]。在frame中,保存有該方法調用的參數、局部變量和返回地址。

堆是JVM中一塊可自由分配給對象的區域。當我們談論垃圾回收(garbage collection)時,我們主要回收堆(heap)的空間。

深入類鎖和對象鎖

請參考:https://blog.csdn.net/javazejian/article/details/72828483

synchronized(""){
;
}

類鎖實際上用對象鎖來實現。當虛擬機裝載一個class文件的時候,它就會創建一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。

一個線程可以多次對同一個對象上鎖。對於每一個對象,java虛擬機維護一個加鎖計數器,線程每獲得一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值為0時,鎖就被完全釋放了。

1568083352926
可以看到,這樣就解決了資源沖突,但是出現了負數

原因;進入循環后等待鎖,資源已經執行到0了,解鎖后依然執行

解決方法:

? 在鎖內部再做一個判斷,將這種情況篩出去

1568083589402
等待的線程在鎖池里面。解鎖后第一個拿到鎖標記的線程變成就緒狀態,再和其他處於就緒狀態的線程爭搶CPU時間片

鎖的類型:

	* 對象鎖
	* 類鎖

多個線程看到的鎖需要時同一把,例如Synchronized()括號中不能寫New對象


同步方法

用關鍵字synchronized修飾的方法就是同步方法

更適用於邏輯比較復雜的情況

方法一:將邏輯獨立出去成為方法,在同步代碼段中調用該方法

1568085004277
真正的同步方法:

1568085262822
添加synchronized關鍵字

同步方法中的鎖:

? 靜態方法:同步鎖就是類鎖:當前類.class

? 非靜態方法:同步鎖是this

顯式鎖


  1. 實例化鎖對象

    ReentranLock lock=new ReentranLock();

  2. 上鎖和解鎖

    lock.lock();//上鎖
    ···//這里寫要執行的邏輯
    lock.unlock();//解鎖
    

死鎖


使用鎖時不要產生死鎖

死鎖:多個線程彼此持有對方所需要的鎖對象,而不釋放自己的鎖,都在等待對方釋放自己所需要的鎖標記

1568125678548
運行結果:

1568125721034
沒有線程可以同時持有A和B。另外,程序到目前為止還是沒有停止的!說明這兩個線程依然在執行!

但依然有不被鎖住的可能性:一個線程在獲得第一個鎖、搶奪第二個鎖的過程中,另外一個線程一直沒有獲得時間片,這樣就有一個線程可以執行完成了

盡量不要讓線程持有了一個鎖標記后再去搶奪另外的鎖

但如果真的有這樣的需求的話:

  1. wait方法:

    wait是Object類中的一個方法,表示讓當前的線程釋放自己的鎖標記並讓出CPU資源,使得當前的線程進入等待隊列

  2. notify(通知)方法:

    喚醒等待隊列中的一個線程,使這個線程進入鎖池。但是具體喚醒哪一個線程不能由軟件編寫者決定,而要由CPU決定

  3. notifyAll方法

    與2相比的區別是喚醒等待隊列中所有等待該鎖的線程

"A".wait();//釋放已經持有的A鎖標記並進入等待隊列

如果該線程沒有A鎖,會爆出異常:

1568126823695
上面的語句會出異常,應該catch:

1568126605458
這樣可以使一個線程完成,但另外一個卻不能

解決方法是在完成的那個線程的邏輯中加入"A".notify();將等待的那個線程喚醒


多線程環境中的單例設計模式

單例設計模式在多線程環境下會出問題!

懶漢式方式會出問題:多個對象會被創建而不是一個

原因:實例化之前失去時間片,另外一個線程又創建了一遍

解決方法1:在實例化之前加一個線程鎖(對象鎖)

1568131498106
解決方法2:將方法該我同步方法(類鎖)

1568131567224

設計模式之三:生產者-消費者設計模式

應用:購票-票庫、就餐-后廚

臨界資源:產品

兩個重要角色:生產者與消費者

  • 生產者:生產邏輯:通過一個生產標記,判斷是否要生產產品。如果要生產,則生產並通知消費者消費;如果不需要,則等待
  • 消費者:消費邏輯:通過判斷是否有足夠的產品可以消費,如果可以,就獲取;否則就等待

生產過程和消費過程是同時執行的,所以需要使用多線程

  1. 做一個產品類
  2. 做一個產品池類:將所有產品進行統一管理,消費和生產都要管理。存儲所有的產品的集合。最后要對產品池進行實例化.臨界資源就是產品池
  3. 涉及到臨界資源讀取的方法要改為同步方法
  4. 做一個生產者和消費者類

代碼如下:

Program.java:

package com.jiading;
/*
*@author JiaDing
*/
public class Program {
	public static void main(String[]args) {
		ProductPool  pool=new ProductPool(15);
		new Productor(pool).start();
		new Customer(pool).start();
	}
}

Product.java:

package com.jiading;
/*
*@author JiaDing
*/
public class Product {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name=name;
	}
	public Product(String name) {
		this.name=name;
	}
	
}

ProductPool.java:

package com.jiading;

import java.util.LinkedList;
import java.util.List;

/*
*@author JiaDing
*/
public class ProductPool {
	private List<Product> productList;
	private int maxSize=0;
	public ProductPool(int maxSize) {
		this.productList=new LinkedList<Product>();
		this.maxSize=maxSize;
	}
	public synchronized void push(Product product) {
		if(this.productList.size()==maxSize) {
			try {
				this.wait();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		this.productList.add(product);
		//通知其他人,有產品可以消費了
		this.notifyAll();
	}
	public synchronized Product pop() {
		if(this.productList.size()==0) {
			try {
				this.wait();
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		Product product=this.productList.remove(0);
		this.notifyAll();
		return product;
	}
	
}

Productor.java:

package com.jiading;
/*
*@author JiaDing
*/
/*
 * 繼承自Thread類,實現多線程
 */
public class Productor extends Thread{
	private ProductPool pool;
	public Productor(ProductPool pool) {
		this.pool=pool;
	}
	@Override
	public void run() {
		while(true) {
			String name=(int)(Math.random()*100)+"號產品";
			Product product=new Product(name);
			this.pool.push(product);
			System.out.println("生成了一件產品:"+name);
		}
	}
}

Customer.java:

package com.jiading;
/*
*@author JiaDing
*/
public class Customer extends Thread{
	private ProductPool pool;
	public Customer(ProductPool pool) {
		this.pool=pool;
	}
	@Override
	public void run() {
		while(true) {
			Product product=this.pool.pop();
			System.out.println("消費者消費了一件產品:"+product.getName());
		
		}
	}
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM