JAVA線程
線程
串行和並發
進程之間資源不共享,所以在程序中一般不單獨開辟進程
線程是一個任務執行的最小單元
線程的並發和進程是一樣的,也是CPU通過中斷進行“假並發”
多個線程同時訪問的資源叫臨界資源
線程的狀態
題外話:時間片
時間片(timeslice)又稱為“量子(quantum)”或“處理器片(processor slice)”是分時操作系統分配給每個正在運行的進程微觀上的一段CPU時間(在搶占內核中是:從進程開始運行直到被搶占的時間)。時間片通常很短(在Linux上為5ms-800ms)
時間片由操作系統內核的調度程序分配給每個進程。首先,內核會給每個進程分配相等的初始時間片(在Linux系統中,初始時間片也不相等,而是各自父進程的一半),然后每個進程輪番地執行相應的時間,當所有進程都處於時間片耗盡的狀態時,內核會重新為每個進程計算並分配時間片,如此往復。
系統通過測量進程處於“睡眠”和“正在運行”狀態的時間長短來計算每個進程的交互性,交互性和每個進程預設的靜態優先級(Nice值)的疊加即是動態優先級,動態優先級按比例縮放就是要分配給那個進程時間片的長短。一般地,為了獲得較快的響應速度,交互性強的進程(即趨向於IO消耗型)被分配到的時間片要長於交互性弱的(趨向於處理器消耗型)進程。
搶占式多任務處理(Preemption)是計算機操作系統中,一種實現多任務處理(multi task)的方式,相對於協作式多任務處理而言。協作式環境下,下一個進程被調度的前提是當前進程主動放棄時間片;搶占式環境下,操作系統完全決定進程調度方案,操作系統可以剝奪耗時長的進程的時間片,提供給其它進程。
- 每個任務賦予唯一的一個優先級(有些操作系統可以動態地改變任務的優先級);
- 假如有幾個任務同時處於就緒狀態,優先級最高的那個將被運行;
- 只要有一個優先級更高的任務就緒,它就可以中斷當前優先級較低的任務的執行;
線程的概念
一個線程就是一個程序內部的順序控制流
線程和進程的區別


java.lang.Thread模擬CPU實現線程,所要執行的代碼和處理的數據要傳遞給Thread類。
構造線程的方法:線程實例化的兩種方式
- 法一:線程實例化
-
繼承Thread類,做一個線程子類(自定義的線程類)
-
重寫run方法,將需要並發執行的任務寫到run中
-
線程開辟需要調用start方法,使線程啟動,來執行run中的邏輯(如果直接調用run方法,這不會開辟新線程,或者線程不會進入就緒態,沒有實現多線程)
多線程的效果:

主線程先結束,再執行邏輯這種方式可讀性更強
缺點:由於JAVA的單繼承,則該類無法再繼承其他類,會對原有的繼承結構造成影響
-
法二:通過Runnable接口


用Runnable”聲明“了對象之后,要真正的實例化Thread對象,即Thread t2=new Thread(r1);這個語句不影響原有繼承關系
缺點:可讀性較差
線程的常用操作
-
線程的命名
-
法一:使用setName命名

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

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

-
-
線程的休眠
線程休眠方法:
Thread.sleep();//參數是以毫秒為單位的時間差

休眠會拋出一個InterruptedException異常,我們需要捕獲它線程休眠可以使一個線程由運行狀態變成阻塞態,休眠時間結束后回到就緒態,如果成功獲得時間片就變成運行狀態
線程的優先級
設置線程的優先級,只是修改這個線程可以去搶到CPU時間片的概率,並不是說優先級高的線程就一定能搶到CPU時間片
? 優先級是一個0-10的整數,默認是5
? 設置優先級必須放到這個線程開始執行(即Start)之前
? 題外話:Thread.currentThread().getName();//獲取當前線程
? 線程之間都是交替執行的,並不是優先級高的先執行完再執行優先級低的

在主方法中:

線程的禮讓(Yield)
禮讓:由執行到就緒態,即讓當前運行狀態的線程釋放自己的CPU資源
注意,放棄當前CPU時間片后,該線程和其他線程一起爭搶時間片,依然有可能搶到,所以禮讓的結果不一定是運行其他線程
yield是一個靜態方法,直接Thread.yield();就可
示例:


注意看,無論是線程1還是線程2,執行到3時都進行了禮讓,讓另一個線程執行(這是比較極端的狀態)
線程中的臨界資源問題
被多個線程要同時訪問的資源是臨界資源
多個線程同時訪問一個臨界資源會出現臨界資源問題,即臨界資源調用出錯
錯誤示例:


執行結果:

可以看到,這里對於restCount的調用是有問題的
錯誤原因
每個線程在字符串拼接、輸出之前時間片就被搶走,另一個線程訪問的是原來的線程做減法之后的restCount,最后誰先輸出不一定。

我們可以看到,這種差異是非常明顯的:當線程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時,鎖就被完全釋放了。

可以看到,這樣就解決了資源沖突,但是出現了負數
原因;進入循環后等待鎖,資源已經執行到0了,解鎖后依然執行
解決方法:
? 在鎖內部再做一個判斷,將這種情況篩出去

等待的線程在鎖池里面。解鎖后第一個拿到鎖標記的線程變成就緒狀態,再和其他處於就緒狀態的線程爭搶CPU時間片
鎖的類型:
* 對象鎖
* 類鎖
多個線程看到的鎖需要時同一把,例如Synchronized()括號中不能寫New對象
同步方法
用關鍵字synchronized修飾的方法就是同步方法
更適用於邏輯比較復雜的情況
方法一:將邏輯獨立出去成為方法,在同步代碼段中調用該方法

真正的同步方法:

添加synchronized關鍵字
同步方法中的鎖:
? 靜態方法:同步鎖就是類鎖:當前類.class
? 非靜態方法:同步鎖是this
顯式鎖
-
實例化鎖對象
ReentranLock lock=new ReentranLock(); -
上鎖和解鎖
lock.lock();//上鎖 ···//這里寫要執行的邏輯 lock.unlock();//解鎖
死鎖
使用鎖時不要產生死鎖
死鎖:多個線程彼此持有對方所需要的鎖對象,而不釋放自己的鎖,都在等待對方釋放自己所需要的鎖標記

運行結果:

沒有線程可以同時持有A和B。另外,程序到目前為止還是沒有停止的!說明這兩個線程依然在執行!
但依然有不被鎖住的可能性:一個線程在獲得第一個鎖、搶奪第二個鎖的過程中,另外一個線程一直沒有獲得時間片,這樣就有一個線程可以執行完成了
盡量不要讓線程持有了一個鎖標記后再去搶奪另外的鎖
但如果真的有這樣的需求的話:
-
wait方法:
wait是Object類中的一個方法,表示讓當前的線程釋放自己的鎖標記並讓出CPU資源,使得當前的線程進入等待隊列
-
notify(通知)方法:
喚醒等待隊列中的一個線程,使這個線程進入鎖池。但是具體喚醒哪一個線程不能由軟件編寫者決定,而要由CPU決定
-
notifyAll方法
與2相比的區別是喚醒等待隊列中所有等待該鎖的線程
"A".wait();//釋放已經持有的A鎖標記並進入等待隊列
如果該線程沒有A鎖,會爆出異常:

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

這樣可以使一個線程完成,但另外一個卻不能
解決方法是在完成的那個線程的邏輯中加入"A".notify();將等待的那個線程喚醒
多線程環境中的單例設計模式
單例設計模式在多線程環境下會出問題!
懶漢式方式會出問題:多個對象會被創建而不是一個
原因:實例化之前失去時間片,另外一個線程又創建了一遍
解決方法1:在實例化之前加一個線程鎖(對象鎖)

解決方法2:將方法該我同步方法(類鎖)
設計模式之三:生產者-消費者設計模式
應用:購票-票庫、就餐-后廚
臨界資源:產品
兩個重要角色:生產者與消費者
- 生產者:生產邏輯:通過一個生產標記,判斷是否要生產產品。如果要生產,則生產並通知消費者消費;如果不需要,則等待
- 消費者:消費邏輯:通過判斷是否有足夠的產品可以消費,如果可以,就獲取;否則就等待
生產過程和消費過程是同時執行的,所以需要使用多線程
- 做一個產品類
- 做一個產品池類:將所有產品進行統一管理,消費和生產都要管理。存儲所有的產品的集合。最后要對產品池進行實例化.臨界資源就是產品池
- 涉及到臨界資源讀取的方法要改為同步方法
- 做一個生產者和消費者類
代碼如下:
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());
}
}
}




