Java學(xué)習(xí)之線程同步與線程間通信詳解
線程同步的概念
由于同一個(gè)進(jìn)程的多個(gè)線程共享同一塊存儲空間,在帶來方便的同時(shí),也會帶來訪問沖突的問題:
舉例:
public class Runnable_test implements Runnable {//實(shí)現(xiàn)Runnable接口 private int ticknumbers=10; @Override public void run() { while(true){ if(ticknumbers<=0){ break; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticknumbers--+"票");//currentThread()監(jiān)測線程的狀態(tài) } } public static void main(String[] args) { Runnable_test runnable_test=new Runnable_test(); new Thread(runnable_test,"小明").start(); new Thread(runnable_test,"小黃").start(); new Thread(runnable_test,"小紅").start(); } }
在輸出的數(shù)據(jù)中,顯然出現(xiàn)了,一張票同時(shí)被大于1人拿到的情況,這與我們的現(xiàn)實(shí)顯然不相符合。
為了解決此問題,Java 語言提供專門的機(jī)制來避免同一個(gè)對象被多個(gè)線程同時(shí)訪問,這個(gè)機(jī)制就是線程同步。
當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問同一個(gè)變量,并且有線程需要修改這個(gè)變量時(shí),就必須采用同步的機(jī)制對其進(jìn)行控制,否則就會出現(xiàn)邏輯錯(cuò)誤的運(yùn)行結(jié)果
造成上述這種錯(cuò)誤邏輯結(jié)果的原因是:可能有多個(gè)線程取得的是同一個(gè)值,各自修改并存入,從而造成修改慢的后執(zhí)行的線程把執(zhí)行快的線程的修改結(jié)果覆蓋掉了
因?yàn)榫€程在執(zhí)行過程中不同步,多個(gè)線程在訪問同一資源時(shí),需要進(jìn)行同步操作,被訪問的資源稱為共享資源。
同步的本質(zhì)是加鎖,Java 中的任何一個(gè)對象都有一把鎖以及和這個(gè)鎖對應(yīng)的等待隊(duì)列,當(dāng)線程要訪問共享資源時(shí),首先要對相關(guān)的對象進(jìn)行加鎖
如果加鎖成功,線程對象才能訪問共享資源并且在訪問結(jié)束后,要釋放鎖:如果加鎖不成功,那么線程進(jìn)入被加鎖對象對應(yīng)的是等待隊(duì)列。
Java用synchronized關(guān)鍵字給針對共享資源進(jìn)行操作的方法加鎖。每個(gè)鎖只有一把鑰匙,只有得到這把鑰匙之后才可以對被保護(hù)的資源進(jìn)行操作,而其他線程只能等待,直到拿到這把鑰匙。
實(shí)現(xiàn)同步的具體方式有同步代碼塊和同步方法兩種
同步代碼塊
使用 synchronized 關(guān)鍵字聲明的代碼塊稱為同步代碼塊。
在任意時(shí)刻,只能有一個(gè)線程訪問同步代碼塊中的代碼,所以同步代碼塊也稱為互斥代碼塊
同步代碼塊格式如下所示:
synchronized(同步對象){
//需要同步的代碼,對共享資源的訪問
}
synchronized關(guān)鍵字后面括號內(nèi)的對象就是被加載的對象,同步代碼塊要實(shí)現(xiàn)對共享資源的訪問
對上述實(shí)例進(jìn)行修改:
package Runnable; public class Runnable_test implements Runnable {//實(shí)現(xiàn)Runnable接口 private int ticknumbers = 20; private Object obj = new Object();//被加鎖的對象,同步對象 @Override public void run() { while (true) { synchronized (obj) { if (ticknumbers > 0) { System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()監(jiān)測線程的狀態(tài) try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } else break; } } } } class test{ public static void main(String[] args) { Runnable_test runnable_test=new Runnable_test(); new Thread(runnable_test,"小明").start(); new Thread(runnable_test,"小黃").start(); new Thread(runnable_test,"小紅").start(); } }
將票數(shù)產(chǎn)生變化的代碼塊修改為同步代碼塊:
修改過后輸出,我們發(fā)現(xiàn),并未出現(xiàn)同一張票,被第二個(gè)甚至第三個(gè)人拿到的情況:
小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小紅-->拿到了第14票
小紅-->拿到了第13票
小紅-->拿到了第12票
小黃-->拿到了第11票
小黃-->拿到了第10票
小黃-->拿到了第9票
小黃-->拿到了第8票
小紅-->拿到了第7票
小紅-->拿到了第6票
小紅-->拿到了第5票
小紅-->拿到了第4票
小紅-->拿到了第3票
小紅-->拿到了第2票
小紅-->拿到了第1票
在上面的修改中,僅僅是將需要互斥的代碼放人了同步塊中。此時(shí),在抽票的過程中通過給同一個(gè) obj對象加鎖來實(shí)現(xiàn)互斥,從而保證線程的同步執(zhí)行。
同步方法
synchronized關(guān)鍵字也可以出現(xiàn)在方法的聲明部分,該方法稱為同步方法
當(dāng)多個(gè)線程對象同時(shí)訪問共享資源時(shí),只有獲得鎖對象的線程才能進(jìn)入同步方法執(zhí)行,其他訪問共享資源的線程將會進(jìn)入鎖對象的等待隊(duì)列,執(zhí)行完同步方法的線程會釋放鎖。
[權(quán)限訪問限定] synchronized 方法返回值 方法名稱(參數(shù)列表){
//.............需要同步的代碼,對共享資源的訪問
}
package Runnable; public class Runnable_test implements Runnable {//實(shí)現(xiàn)Runnable接口 private int ticknumbers = 20; @Override public void run() { while (true) { if (ticknumbers > 0) { ticks();//調(diào)用同步方法 } else break; } } //同步方法 public synchronized void ticks(){ if (ticknumbers > 0) { System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票"); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } //測試類 class test{ public static void main(String[] args) { Runnable_test runnable_test=new Runnable_test(); new Thread(runnable_test,"小明").start(); new Thread(runnable_test,"小黃").start(); new Thread(runnable_test,"小紅").start(); } }
輸出:
小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小黃-->拿到了第15票
小黃-->拿到了第14票
小黃-->拿到了第13票
小黃-->拿到了第12票
小黃-->拿到了第11票
小黃-->拿到了第10票
小黃-->拿到了第9票
小紅-->拿到了第8票
小紅-->拿到了第7票
小紅-->拿到了第6票
小紅-->拿到了第5票
小紅-->拿到了第4票
小紅-->拿到了第3票
小紅-->拿到了第2票
小紅-->拿到了第1票
同步方法的本質(zhì)也是給對象加鎖,但是是給同步方法所在類的 this 對象加鎖,所以在上述實(shí)例中,我們就刪除了obj對象的定義。
package Runnable; public class Runnable_test implements Runnable {//實(shí)現(xiàn)Runnable接口 private int ticknumbers = 20; boolean tag = false;//設(shè)置此變量的作用是為了讓一個(gè)線程進(jìn)入同步塊,另一個(gè)線程進(jìn)入同步方法 @Override public void run() { if(tag){ while(true) ticks(); } else{ while (true) { synchronized (this) { if (ticknumbers > 0) { System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()監(jiān)測線程的狀態(tài) try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } else break; } } } } //同步方法 public synchronized void ticks() { if (ticknumbers > 0) { System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } else return; } } //測試類 class test { public static void main(String[] args) throws InterruptedException { Runnable_test runnable_test = new Runnable_test(); Thread thread1=new Thread(runnable_test, "小明"); thread1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } runnable_test.tag=true; Thread thread2=new Thread(runnable_test, "小黃"); thread2.start(); } }
輸出:
小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小明-->拿到了第14票
小明-->拿到了第13票
小明-->拿到了第12票
小明-->拿到了第11票
小明-->拿到了第10票
小明-->拿到了第9票
小明-->拿到了第8票
小明-->拿到了第7票
小黃-->拿到了第6票
小黃-->拿到了第5票
小黃-->拿到了第4票
小黃-->拿到了第3票
小黃-->拿到了第2票
小黃-->拿到了第1票
通過程序運(yùn)行結(jié)果可以看出:線程thread1執(zhí)行同步代碼塊,線程thread2執(zhí)行同步方法,兩個(gè)線程之間形成了同步。
由于同步代碼塊是給 this對象加鎖,所以表明同步方法也是給 this對象加鎖,否則,兩者之間不能形成同步。
注意:多線程的同步程序中,不同的線程對象必須給同一個(gè)對象加鎖,否則這些線程對象之間無法實(shí)現(xiàn)同步
線程組
線程組可以看作是包含了許多線程的對象集,它擁有一個(gè)名字以及一些相關(guān)的屬性,可以當(dāng)作一個(gè)組來管理其中的線程。
每個(gè)線程都是線程組的一個(gè)成員,線程組把多個(gè)線程集成一個(gè)對象,通過線程組可以同時(shí)對其中的多個(gè)線程進(jìn)行操作。在生成線程時(shí)必須將線程放到指定的線程組,也可以放在缺省的線程組中,缺省的就是生成該線程的線程所在的線程組。一旦一個(gè)線程加入了某個(gè)線程組,就不能被移出這個(gè)組。
java,lang包的ThreadGroup類表示線程組,在創(chuàng)建線程之前,可以創(chuàng)建一個(gè)ThreadGroup對象。
下面代碼是創(chuàng)建線程組并在其中加人兩個(gè)線程
ThreadGroup myThreadGroup = new ThreadGroup("a"); //創(chuàng)建線程組 //將下述兩個(gè)線程加入其中 Thread myThread1 = new Thread(myThreadGroup,"worker1"); Thread myThread2 = new Thread(myThreadGroup,"worker2"); myThread1.start(); myThread2.start();
線程組的相關(guān)方法
String getName(); //返回線程組的名字 ThreadGoup getParent(); //返回父線程 int tactiveCount(); //返回線程組中當(dāng)前激活的線程的數(shù)目,包括子線程組中的活動(dòng)線程 int enumerate(Thread list[]) //將所有線程組中激活的線程復(fù)制到一個(gè)線程數(shù)組中 void setMaxPriority(int pri) //設(shè)置線程的最高優(yōu)先級,pri是該線程組的新優(yōu)先級 void interrupt() //向線程組及其子組中的線程發(fā)送一個(gè)中斷信息 boolean isDaemon() //判斷是否為Daemon線程組 boolean parentOf(ThreadGoup g) //判斷線程組是否是線程g或g的子線程 toString() //返回一個(gè)表示本線程組的字符串
線程組對象的基本應(yīng)用
舉例:
package Runnable; public class MyThreadgroup { public void test(){ ThreadGroup threadGroup=new ThreadGroup("test"); //創(chuàng)建名為test的線程組 Thread A=new Thread(threadGroup,"線程A"); Thread B=new Thread(threadGroup,"線程B"); Thread C=new Thread(threadGroup,"線程C"); //為線程設(shè)置優(yōu)先級 A.setPriority(6); C.setPriority(4); A.start(); B.start(); C.start(); System.out.println("threadGroup正在進(jìn)行活動(dòng)的個(gè)數(shù):"+threadGroup.activeCount()); System.out.println("線程A的優(yōu)先級:"+A.getPriority()); System.out.println("線程B的優(yōu)先級:"+B.getPriority()); System.out.println("線程C的優(yōu)先級:"+C.getPriority()); } } class MyThreadgroup_test{ public static void main(String[] args) { MyThreadgroup myThreadgroup=new MyThreadgroup(); myThreadgroup.test(); } }
輸出:
threadGroup正在進(jìn)行活動(dòng)的個(gè)數(shù):3
線程A的優(yōu)先級:6
線程B的優(yōu)先級:5
線程C的優(yōu)先級:4
線程間的通信
某些情況下,多個(gè)線程之間需要相互配合來完成一件事情,這些線程之間就需要進(jìn)行通信”,把一方線程的執(zhí)行情況告訴給另一方線程。
“通信”的方法在 java.lang.Object類中定義了,我們可以通過“生產(chǎn)者-消費(fèi)者”模型來理解線程間的通信。
有兩個(gè)線程對象,其中一個(gè)是生產(chǎn)者,另一個(gè)是消費(fèi)者。生產(chǎn)者線程負(fù)責(zé)生產(chǎn)產(chǎn)品并放入產(chǎn)品緩沖區(qū),消費(fèi)者線程負(fù)責(zé)從產(chǎn)品緩沖區(qū)取出產(chǎn)品并消費(fèi)。
當(dāng)生產(chǎn)者線程獲得 CPU 使用權(quán)后:
先判斷產(chǎn)品緩沖區(qū)是否有產(chǎn)品,如果有產(chǎn)品就調(diào)用 wait()方法進(jìn)入產(chǎn)品緩沖區(qū)對象的等待隊(duì)列并釋放產(chǎn)品緩沖區(qū)對象的鎖;如果發(fā)現(xiàn)產(chǎn)品緩沖區(qū)中沒有產(chǎn)品,就生產(chǎn)產(chǎn)品并放入緩沖區(qū)并調(diào)用notify()方法發(fā)送通知給消費(fèi)者線程。
當(dāng)消費(fèi)者線程獲得CPU使用權(quán)后:
先判斷產(chǎn)品緩沖區(qū)是否有產(chǎn)品,如果有產(chǎn)品就拿出來消費(fèi)并調(diào)用 notify()方法發(fā)送通知給生產(chǎn)者線程;如果發(fā)現(xiàn)產(chǎn)品緩沖區(qū)中沒有產(chǎn)品,調(diào)用 wait()方法進(jìn)入產(chǎn)品緩沖區(qū)對象的等待隊(duì)列并釋放產(chǎn)品緩沖區(qū)對象的鎖。
注意:線程間通信是建立在線程同步基礎(chǔ)上的,所以wait()notify()和notifyAll()方法的調(diào)用要出現(xiàn)在同步代碼塊或同步方法中
線程通信簡單應(yīng)用
package Runnable; class Box {//產(chǎn)品緩沖區(qū) public String name="蘋果";//表示產(chǎn)品的名稱 public boolean isFull=true;//表示當(dāng)前緩沖區(qū)中是否有產(chǎn)品 } //定義消費(fèi)者類 class Cossumer implements Runnable { Box box; Cossumer(Box box) { this.box = box; } @Override public void run() { while (true) { synchronized (box) {//對產(chǎn)品緩沖區(qū)對象加鎖 if (box.isFull == true) //緩沖區(qū)中有產(chǎn)品 { System.out.println("消費(fèi)者拿出----:" + box.name); box.isFull = false;//設(shè)置緩沖區(qū)中產(chǎn)品為空 box.notify();//發(fā)送通知給生產(chǎn)者線程對象 } else { try { //消費(fèi)者線程進(jìn)入產(chǎn)品緩沖區(qū)的等待隊(duì)列并釋放鎖 box.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } //生產(chǎn)者類 class product implements Runnable{ Box box; int Count=0; public product(Box box) { this.box=box; } @Override public void run() { while(true){ synchronized (box)//對產(chǎn)品緩沖區(qū)對象加鎖 { if(box.isFull==true)//緩沖區(qū)中有產(chǎn)品 { try { box.wait();//生產(chǎn)者線程進(jìn)入等待隊(duì)列并釋放鎖 } catch (InterruptedException e) { e.printStackTrace(); } } else { if (Count == 0) { box.name = "香蕉"; System.out.println("生產(chǎn)者放入+++++:" + box.name); } else { box.name = "蘋果"; System.out.println("生產(chǎn)者放入+++++:" + box.name); } Count=(Count+1)%2; box.isFull=true;//設(shè)置緩沖區(qū)中有產(chǎn)品 box.notify();//發(fā)送通知給消費(fèi)者線程對象 } } } } } class box_test{ public static void main(String[] args) { Box box=new Box();//創(chuàng)建產(chǎn)品緩沖區(qū)對象 product product=new product(box); Cossumer cossumer=new Cossumer(box);//生產(chǎn)者和消費(fèi)者對象要共享同一個(gè)產(chǎn)品緩沖區(qū) Thread thread1=new Thread(product);//創(chuàng)建生產(chǎn)者線程對象 Thread thread2=new Thread(cossumer);//創(chuàng)建消費(fèi)者線程對象 thread1.start();//啟動(dòng)生產(chǎn)者線程對象 thread2.start();//啟動(dòng)消費(fèi)者線程對象 } }
輸出:
消費(fèi)者拿出----:香蕉
生產(chǎn)者放入+++++:蘋果
消費(fèi)者拿出----:蘋果
生產(chǎn)者放入+++++:香蕉
消費(fèi)者拿出----:香蕉
生產(chǎn)者放入+++++:蘋果
消費(fèi)者拿出----:蘋果
生產(chǎn)者放入+++++:香蕉
消費(fèi)者拿出----:香蕉
生產(chǎn)者放入+++++:蘋果
消費(fèi)者拿出----:蘋果
生產(chǎn)者放入+++++:香蕉
從運(yùn)行結(jié)果可以看出:生產(chǎn)者線程向緩沖區(qū)放入什么產(chǎn)品,消費(fèi)者就從緩沖區(qū)中取出什么產(chǎn)品,生產(chǎn)者生產(chǎn)一個(gè)產(chǎn)品,消費(fèi)者就消費(fèi)一個(gè)產(chǎn)品,兩者之間實(shí)現(xiàn)了通信.
以上就是Java學(xué)習(xí)之線程同步與線程間通信詳解的詳細(xì)內(nèi)容,更多關(guān)于Java線程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何解決java:錯(cuò)誤:無效的源發(fā)行版:17問題
這篇文章主要介紹了如何解決java:錯(cuò)誤:無效的源發(fā)行版:17問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07- 本文給大家分享的是一則使用java編寫的文件管理器的代碼,新人練手的作品,邏輯上還是有點(diǎn)小問題,大家?guī)兔纯窗伞?/div> 2015-04-04
Java 自動(dòng)安裝校驗(yàn)TLS/SSL證書
這篇文章主要介紹了Java 自動(dòng)安裝校驗(yàn)TLS/SSL證書的示例,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-10-10java 數(shù)據(jù)庫連接與增刪改查操作實(shí)例詳解
這篇文章主要介紹了java 數(shù)據(jù)庫連接與增刪改查操作,結(jié)合實(shí)例形式詳細(xì)分析了java使用jdbc進(jìn)行數(shù)據(jù)庫連接及增刪改查等相關(guān)操作實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2019-11-11關(guān)于Java整合RabbitMQ實(shí)現(xiàn)生產(chǎn)消費(fèi)的7種通訊方式
這篇文章主要介紹了關(guān)于Java整合RabbitMQ實(shí)現(xiàn)生產(chǎn)消費(fèi)的7種通訊方式,消息中間件是基于隊(duì)列與消息傳遞技術(shù),在網(wǎng)絡(luò)環(huán)境中為應(yīng)用系統(tǒng)提供同步或異步、可靠的消息傳輸?shù)闹涡攒浖到y(tǒng),需要的朋友可以參考下2023-05-05最新評論