
🧡💛💚💙💜🤎💗🧡💛💚💙💜🤎💗
感謝各位一直以來的支持和鼓勵 , 制作不易
🙏 求點贊 👍 ? 收藏 ? ? 關注?
一鍵三連走起 ! ! !
🧡💛💚💙💜🤎💗 🧡💛💚💙💜🤎💗
【友情鏈接】???執行緒入門學習,執行緒(多執行緒)的實作
【友情鏈接】???執行緒入門學習,執行緒池 & Lambda運算式
一、執行緒安全的概述
如果有多個執行緒在同時運行,并且這些執行緒可能會同時運行一段相同的代碼,程式每次運行結果和單執行緒運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,這樣就是執行緒安全的,
二、執行緒同步機制
當我們使用多個執行緒訪問同一個資源的時候,且多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題, 解決多錢程并發訪問一個資源的安全性問題,Java中提供了同步機制(synchronized)來解決,
Java中有三種方法實作同步操作:同步代碼塊,同步方法,鎖機制,
案例,使用多執行緒模擬電影院賣票,賣100張票,有三個售票視窗
//通過實作Runnable介面來實作多執行緒
public class RunnableImp implements Runnable {
//設定總票數,100張票
private int ticket=100;
//重寫run方法,設定執行緒任務,賣票
@Override
public void run() {
//死回圈,重復賣票
while (true){
//查看票有沒有賣完
if (ticket>0){
//沒賣完,繼續賣票
System.out.println(Thread.currentThread().getName()+"--正在賣第"+ticket+"張票");
ticket--;
}
}
}
}
//模擬賣票
public class test {
public static void main(String[] args) {
RunnableImp runnableImp = new RunnableImp();
Thread thread = new Thread(runnableImp);
thread.start();//1號視窗
new Thread(runnableImp).start();//2號視窗
new Thread(runnableImp).start();//3號視窗
}
}
測驗結果:
會執行緒安全問題1:售出相同的票
會執行緒安全問題2:售出不存在的票

1、同步代碼塊
使用格式:
synchronize(鎖物件){
//可能出現執行緒安全的代碼塊
}
注意:
1,鎖物件可以是任意的資料型別
2,多個執行緒物件要使用同一把鎖
鎖物件的作用:
把同步代碼塊鎖住,只讓一個執行緒在同步代碼塊中執行
示例代碼;
//通過實作Runnable介面來實作多執行緒
public class RunnableImp implements Runnable {
//設定總票數,100張票
private int ticket=100;
//創建一個物件
Object obj=new Object();
//重寫run方法,設定執行緒任務,賣票
@Override
public void run() {
//死回圈,重復賣票
while (true){
//設定同步代碼塊
synchronized (obj){
//查看票有沒有賣完
if (ticket>0){
//沒賣完,繼續賣票
System.out.println(Thread.currentThread().getName()+"--正在賣第"+ticket+"張票");
ticket--;
}
}
}
}
}
運行test測驗類,得到測驗結果:


由測驗結果,可以發現解決了賣出重復票和不存在的票的執行緒安全問題,
同步代碼塊實作同步技術的原理分析:
同步代碼塊中使用了一個鎖物件也叫做(“同步鎖”、“物件鎖”、“物件監視器”)在多執行緒實作售票的案例中,有三個執行緒一起搶奪CPU的執行權,哪個執行緒搶奪到了CPU執行權,哪個就執行run()方法,當遇到了同步代碼塊synchronized時,就會檢查代碼塊中是否有鎖物件,如果有那么就會獲取到該物件,并進入代碼塊中執行;那么剩下的執行緒,若是也有搶到了CPU的執行權,也會執行synchronized同步代碼塊并且判斷是否有鎖物件,若發現沒有,那么此執行緒就會進入到阻塞狀態,直到獲取了鎖物件的執行緒歸還鎖物件,也就是一直等到上一個執行緒執行完同步代碼塊中的代碼并把鎖物件歸還到同步代碼塊中,此執行緒才能進入到同步中執行代碼,
所以同步技術的核心就是,同步中的執行緒沒有執行完畢不會釋放鎖物件;而同步外的執行緒沒有鎖物件則進不去同步
2、同步方法
解決執行緒安全問題的二種方案:使用同步方法
作用:
同步方法會將方法內部的代碼鎖住,只讓一個執行緒使用,
使用步驟:
1.將訪問了共享資料的代碼也就是可能出現執行緒安全問題的代碼抽取出來,
放到一個方法中去
2.給方法添加 synchronized 修飾狩
使用格式:
權限修飾符 synchronized 回傳值型別 方法名(引數串列){
可能會出現執行緒安全問題的代碼(訪問了共享資料的代碼塊 )
}
示例代碼:
//通過實作Runnable介面來實作多執行緒
public class RunnableImp implements Runnable {
//設定總票數,100張票
private int ticket=100;
//重寫run方法,設定執行緒任務,賣票
@Override
public void run() {
//死回圈,重復賣票
while (true){
sellticket();
}
}
//使用同步方法
public synchronized void sellticket(){
//查看票有沒有賣完
if (ticket>0){
//沒賣完,繼續賣票
System.out.println(Thread.currentThread().getName()+"--正在賣第"+ticket+"張票");
ticket--;
}
}
}
運行test測驗代碼:測驗結果和同步代碼塊測驗結果一樣,沒有出現重復票或不存在的票
3、鎖機制
java.util.concurrent.locks.Lock機制提供了比synchronized代碼塊和synchronized方法更加廣泛的鎖定操作, 而且同步代碼塊和同步方法具有的功能Lock也都會有,并且Lock更能體現出面向物件,
Lock鎖也稱為同步鎖,它的加鎖和釋放鎖都已經方法化了:
public void lock(): 加同步鎖,
public void unlock(: 釋放同步鎖.
示例代碼:
解決執行緒安全問題的二種方案:使用Lock鎖
使用步驟:
1.在成員位置創建一個ReentrantLock物件
2.在可能會出現安全問題的代碼前呼叫Lock介面中的方法Lock獲取鎖
3.在可能會出現安全問題的代碼后呼叫Lock介面中的方法unLock釋放鎖
//通過實作Runnable介面來實作多執行緒
public class RunnableImp implements Runnable {
//設定總票數,100張票
private int ticket=100;
//1.在成員位置創建一個ReentrantLock物件
Lock lock = new ReentrantLock();
//重寫run方法,設定執行緒任務,賣票
@Override
public void run() {
//死回圈,重復賣票
while (true){
//2.在可能會出現安全問題的代碼前呼叫Lock介面中的方法Lock獲取鎖
lock.lock();
//查看票有沒有賣完
if (ticket>0){
//沒賣完,繼續賣票
System.out.println(Thread.currentThread().getName()+"--正在賣第"+ticket+"張票");
ticket--;
}
//3.在可能會出現安全問題的代碼后呼叫Lock介面中的方法unLock釋放鎖
lock.unlock();
}
}
}
運行test測驗代碼:測驗結果和同步代碼塊測驗結果一樣,沒有出現重復票或不存在的票
三、執行緒的六種狀態
-
New(新建狀態)
- 執行緒剛被創建時的狀態,但是執行緒還未啟動,還沒呼叫start方法,
-
Runnable(可運行狀態)
- 執行緒可以在java虛擬機中運行的狀態,執行緒可能正在運行自己代碼也可能沒有,取決于作業系統的處理器,
- Blocked(鎖阻塞狀態)
- 當一個執行緒試圖獲取一個物件鎖時,但是該物件鎖已經被其他的執行緒持有,那么該執行緒就會進入Blocked阻塞狀態;而當該執行緒持有鎖物件時,該執行緒就會轉變成Runnable可運行狀態,
- Waiting(無限等待狀態)
- 一個錢程在等待另一個執行緒執行一個(喚醒)動作時,那么該執行緒就是進入Waiting無限等待狀態,進入這個狀態后的執行緒是不能被自動喚醒的,必須要等待另外一個執行緒呼叫notify()或者notifyAll()方法才能夠被喚醍,
- Timed Waiting(計時等待狀態)
- 與waiting無限等待狀態很相似,當呼叫帶有超時引數的方法時,他們就會進入Timed Waiting計時等待狀態,而該狀態將會一直保持著,直到超時期滿或者接收到喚醒通知,帶有超時引數的常用方法有
Thread.sleep() 和 Object.wait(),
- 與waiting無限等待狀態很相似,當呼叫帶有超時引數的方法時,他們就會進入Timed Waiting計時等待狀態,而該狀態將會一直保持著,直到超時期滿或者接收到喚醒通知,帶有超時引數的常用方法有
- Terminated(死亡狀態)
- 已經退出的執行緒處于這種狀態,
四、執行緒等待與喚醒機制
等待喚醒中使用的方法
等待喚醒機制用于解決執行緒間通信的問題,
使用的3個方法如下:
- 1.wait()方法 :呼叫了wait()方法候,執行緒就不再參與調度,進入 wait set中,不會浪費CPU資源,也不會競爭鎖,這種執行緒狀態就是WAITING(等待狀態),同時它還會等著別的執行緒執行特別的動作—>通知( notify) 在這個物件上等待的執行緒就會從walt set中釋放出來,重新進入到調度佇列 ( ready queue)中
- 2.notify()方法 :選擇所通知物件的 wait set 中的一個執行緒并釋放它,也就是喚醒在此監視器上等待的單個執行緒
- 3.notifyAll():釋放所通知物件的wait set 中的全部執行緒,也就是喚醒在此監視器上等待的所有執行緒
注意:被通知的執行緒是不能立即進入到執行狀態的,因為此前該執行緒實在同步塊內中斷的,此時該執行緒已經沒有鎖物件了,所以他需要再次的獲取鎖物件,那么此時就可能會有其他的執行緒與其競爭鎖物件,該執行緒競爭成功i獲得鎖物件后才會在之前呼叫到wait()方法之處恢復執行,
所以可以總結到:若執行緒能夠獲取到鎖,那么執行緒就會從等待狀態轉變成運行狀態;否則執行緒則只是從wait set中出來又進入到entry set中,執行緒就會又從等待狀態轉換到阻塞狀態
呼叫wait()和notify()方法需要注意的細節
- 1.wait方法與notify方法必須要由同一個鎖物件呼叫,因為對應的鎖物件可以通過notify喚醒使用同一個鎖物件呼叫的wait()方法后的執行緒,
- 2.wait()方法與notify()方法都是Object類的方法,因為鎖物件可以是任意型別的物件,而任意類的物件所屬的類都是繼承了Object類的, I
- 3.walt()方法與notlfy()方法必須要在同步代碼塊或者是同步方法中使用,因為要呼叫這兩個方法,必須要通過鎖物件,
案例:客戶去牛肉面館吃牛肉面,面館老板首先是在等待客戶點面,等到了一個客戶點了一碗牛肉面,老板開始做面,客戶則進入等待,等到老板面做好了就會告知客戶面好了,客戶開始吃面,
示例代碼:
//創建牛肉面類,
class BeefNoodles{
//面湯種類:金湯,紅湯,
String tang;
//口味:不辣、一般辣
String kouwei;
//面的狀態,有面true, 沒面false
boolean flag =false;
}
//創建牛肉面館類執行緒
public class BeefNoodlesRestaurant extends Thread {
//創建一個牛肉面類的,牛肉面變數
private BeefNoodles beefNoodles;
//使用有參構造方法給牛肉面變數賦值
public BeefNoodlesRestaurant(BeefNoodles beefnoodles) {
this.beefNoodles = beefnoodles;
}
//重寫run方法,設定執行緒任務:做牛肉面
@Override
public void run() {
//定義變數,決定制作的牛肉面的口味,
int count = 2;//默認金湯不辣
while (true){//死回圈,一直制作牛肉面
//使用同步代碼塊來保護執行緒安全
synchronized (beefNoodles){
//判斷牛肉面的狀態,決定是否進入執行緒等待狀態
if (beefNoodles.flag==true){
//牛肉面已經存在(已做好),beefNoodles呼叫wait()方法進入等待
try{
beefNoodles.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//喚醒執行緒,制作牛肉面
//設定牛肉面的口味
if (count%2==0){
beefNoodles.tang="金湯";
beefNoodles.kouwei="不辣";
}else if (count%3==0){
beefNoodles.tang="紅湯";
beefNoodles.kouwei="不辣";
}else if (count%5==0){
beefNoodles.tang="金湯";
beefNoodles.kouwei="一般辣";
}else if (count%7==0){
beefNoodles.tang="紅湯";
beefNoodles.kouwei="一般辣";
}
//確認牛肉面的種類
System.out.println("正在做"+beefNoodles.tang+beefNoodles.kouwei+"牛肉面");
//制作牛肉面需要的時間6秒
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//牛肉面館制作好了牛肉面
beefNoodles.flag=true;//設定面的狀態
count++;
//喚醒客戶吃面
beefNoodles.notify();
System.out.println("客觀的"+beefNoodles.tang+beefNoodles.kouwei+"牛肉面已經制作好了");
}
}
}
}
//創建客戶類執行緒
public class KeHu extends Thread {
//創建一個牛肉面類的,牛肉面變數
private BeefNoodles beefNoodles;
//使用有參構造方法給牛肉面變數賦值
public KeHu(BeefNoodles beefnoodles) {
this.beefNoodles = beefnoodles;
}
//重寫run方法,設定執行緒任務:吃牛肉面
@Override
public void run() {
int i=1;//客戶序號
while (true){//死回圈,一直吃牛肉面
//使用同步代碼塊來保護執行緒安全
synchronized (beefNoodles){
//判斷牛肉面的狀態,決定是否進入執行緒等待狀態
if (beefNoodles.flag==false){
//牛肉面不存在(沒有做好),beefNoodles呼叫wait()方法進入等待
try {
beefNoodles.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//執行緒喚醒之后執行的狀態
System.out.println("客戶"+Thread.currentThread().getName()+"-->"+i+"正在吃"
+beefNoodles.tang+beefNoodles.kouwei+"的牛肉面");
//客戶吃完牛肉面,修改面的狀態
beefNoodles.flag =false;
//面吃完了,喚醒面館制作面
beefNoodles.notify();
System.out.println("客戶"+Thread.currentThread().getName()+"-->"+i+"已經吃完了"
+beefNoodles.tang+beefNoodles.kouwei+"的牛肉面"+",面館開始繼續制作牛肉面");
i++;//下一位客戶
System.out.println("----------------------------------");
}
}
}
}
//測驗類
public class Test {
public static void main(String[] args) {
//創建一個牛肉面的物件
BeefNoodles beefNoodles = new BeefNoodles();
//開啟牛肉面館執行緒,制作面條
new BeefNoodlesRestaurant(beefNoodles).start();
//開啟客戶執行緒,客戶開始吃面條
new KeHu(beefNoodles).start();
}
}
測驗結果:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292645.html
標籤:其他
