Java學習之線程同步與線程間通信詳解
線程同步的概念
由于同一個進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也會帶來訪問沖突的問題:
舉例:
public class Runnable_test implements Runnable {//實現(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)了,一張票同時被大于1人拿到的情況,這與我們的現(xiàn)實顯然不相符合。
為了解決此問題,Java 語言提供專門的機制來避免同一個對象被多個線程同時訪問,這個機制就是線程同步。
當兩個或多個線程同時訪問同一個變量,并且有線程需要修改這個變量時,就必須采用同步的機制對其進行控制,否則就會出現(xiàn)邏輯錯誤的運行結果

造成上述這種錯誤邏輯結果的原因是:可能有多個線程取得的是同一個值,各自修改并存入,從而造成修改慢的后執(zhí)行的線程把執(zhí)行快的線程的修改結果覆蓋掉了
因為線程在執(zhí)行過程中不同步,多個線程在訪問同一資源時,需要進行同步操作,被訪問的資源稱為共享資源。
同步的本質(zhì)是加鎖,Java 中的任何一個對象都有一把鎖以及和這個鎖對應的等待隊列,當線程要訪問共享資源時,首先要對相關的對象進行加鎖
如果加鎖成功,線程對象才能訪問共享資源并且在訪問結束后,要釋放鎖:如果加鎖不成功,那么線程進入被加鎖對象對應的是等待隊列。
Java用synchronized關鍵字給針對共享資源進行操作的方法加鎖。每個鎖只有一把鑰匙,只有得到這把鑰匙之后才可以對被保護的資源進行操作,而其他線程只能等待,直到拿到這把鑰匙。
實現(xiàn)同步的具體方式有同步代碼塊和同步方法兩種
同步代碼塊
使用 synchronized 關鍵字聲明的代碼塊稱為同步代碼塊。
在任意時刻,只能有一個線程訪問同步代碼塊中的代碼,所以同步代碼塊也稱為互斥代碼塊
同步代碼塊格式如下所示:
synchronized(同步對象){
//需要同步的代碼,對共享資源的訪問
}
synchronized關鍵字后面括號內(nèi)的對象就是被加載的對象,同步代碼塊要實現(xiàn)對共享資源的訪問
對上述實例進行修改:
package Runnable;
public class Runnable_test implements Runnable {//實現(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)同一張票,被第二個甚至第三個人拿到的情況:
小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小紅-->拿到了第14票
小紅-->拿到了第13票
小紅-->拿到了第12票
小黃-->拿到了第11票
小黃-->拿到了第10票
小黃-->拿到了第9票
小黃-->拿到了第8票
小紅-->拿到了第7票
小紅-->拿到了第6票
小紅-->拿到了第5票
小紅-->拿到了第4票
小紅-->拿到了第3票
小紅-->拿到了第2票
小紅-->拿到了第1票
在上面的修改中,僅僅是將需要互斥的代碼放人了同步塊中。此時,在抽票的過程中通過給同一個 obj對象加鎖來實現(xiàn)互斥,從而保證線程的同步執(zhí)行。
同步方法
synchronized關鍵字也可以出現(xiàn)在方法的聲明部分,該方法稱為同步方法
當多個線程對象同時訪問共享資源時,只有獲得鎖對象的線程才能進入同步方法執(zhí)行,其他訪問共享資源的線程將會進入鎖對象的等待隊列,執(zhí)行完同步方法的線程會釋放鎖。
[權限訪問限定] synchronized 方法返回值 方法名稱(參數(shù)列表){
//.............需要同步的代碼,對共享資源的訪問
}
package Runnable;
public class Runnable_test implements Runnable {//實現(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 對象加鎖,所以在上述實例中,我們就刪除了obj對象的定義。
package Runnable;
public class Runnable_test implements Runnable {//實現(xiàn)Runnable接口
private int ticknumbers = 20;
boolean tag = false;//設置此變量的作用是為了讓一個線程進入同步塊,另一個線程進入同步方法
@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票
通過程序運行結果可以看出:線程thread1執(zhí)行同步代碼塊,線程thread2執(zhí)行同步方法,兩個線程之間形成了同步。
由于同步代碼塊是給 this對象加鎖,所以表明同步方法也是給 this對象加鎖,否則,兩者之間不能形成同步。
注意:多線程的同步程序中,不同的線程對象必須給同一個對象加鎖,否則這些線程對象之間無法實現(xiàn)同步
線程組
線程組可以看作是包含了許多線程的對象集,它擁有一個名字以及一些相關的屬性,可以當作一個組來管理其中的線程。
每個線程都是線程組的一個成員,線程組把多個線程集成一個對象,通過線程組可以同時對其中的多個線程進行操作。在生成線程時必須將線程放到指定的線程組,也可以放在缺省的線程組中,缺省的就是生成該線程的線程所在的線程組。一旦一個線程加入了某個線程組,就不能被移出這個組。
java,lang包的ThreadGroup類表示線程組,在創(chuàng)建線程之前,可以創(chuàng)建一個ThreadGroup對象。
下面代碼是創(chuàng)建線程組并在其中加人兩個線程
ThreadGroup myThreadGroup = new ThreadGroup("a"); //創(chuàng)建線程組
//將下述兩個線程加入其中
Thread myThread1 = new Thread(myThreadGroup,"worker1");
Thread myThread2 = new Thread(myThreadGroup,"worker2");
myThread1.start();
myThread2.start();
線程組的相關方法
String getName(); //返回線程組的名字 ThreadGoup getParent(); //返回父線程 int tactiveCount(); //返回線程組中當前激活的線程的數(shù)目,包括子線程組中的活動線程 int enumerate(Thread list[]) //將所有線程組中激活的線程復制到一個線程數(shù)組中 void setMaxPriority(int pri) //設置線程的最高優(yōu)先級,pri是該線程組的新優(yōu)先級 void interrupt() //向線程組及其子組中的線程發(fā)送一個中斷信息 boolean isDaemon() //判斷是否為Daemon線程組 boolean parentOf(ThreadGoup g) //判斷線程組是否是線程g或g的子線程 toString() //返回一個表示本線程組的字符串
線程組對象的基本應用
舉例:
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");
//為線程設置優(yōu)先級
A.setPriority(6);
C.setPriority(4);
A.start();
B.start();
C.start();
System.out.println("threadGroup正在進行活動的個數(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正在進行活動的個數(shù):3
線程A的優(yōu)先級:6
線程B的優(yōu)先級:5
線程C的優(yōu)先級:4
線程間的通信
某些情況下,多個線程之間需要相互配合來完成一件事情,這些線程之間就需要進行通信”,把一方線程的執(zhí)行情況告訴給另一方線程。
“通信”的方法在 java.lang.Object類中定義了,我們可以通過“生產(chǎn)者-消費者”模型來理解線程間的通信。
有兩個線程對象,其中一個是生產(chǎn)者,另一個是消費者。生產(chǎn)者線程負責生產(chǎn)產(chǎn)品并放入產(chǎn)品緩沖區(qū),消費者線程負責從產(chǎn)品緩沖區(qū)取出產(chǎn)品并消費。
當生產(chǎn)者線程獲得 CPU 使用權后:
先判斷產(chǎn)品緩沖區(qū)是否有產(chǎn)品,如果有產(chǎn)品就調(diào)用 wait()方法進入產(chǎn)品緩沖區(qū)對象的等待隊列并釋放產(chǎn)品緩沖區(qū)對象的鎖;如果發(fā)現(xiàn)產(chǎn)品緩沖區(qū)中沒有產(chǎn)品,就生產(chǎn)產(chǎn)品并放入緩沖區(qū)并調(diào)用notify()方法發(fā)送通知給消費者線程。
當消費者線程獲得CPU使用權后:
先判斷產(chǎn)品緩沖區(qū)是否有產(chǎn)品,如果有產(chǎn)品就拿出來消費并調(diào)用 notify()方法發(fā)送通知給生產(chǎn)者線程;如果發(fā)現(xiàn)產(chǎn)品緩沖區(qū)中沒有產(chǎn)品,調(diào)用 wait()方法進入產(chǎn)品緩沖區(qū)對象的等待隊列并釋放產(chǎn)品緩沖區(qū)對象的鎖。
注意:線程間通信是建立在線程同步基礎上的,所以wait()notify()和notifyAll()方法的調(diào)用要出現(xiàn)在同步代碼塊或同步方法中
線程通信簡單應用
package Runnable;
class Box {//產(chǎn)品緩沖區(qū)
public String name="蘋果";//表示產(chǎn)品的名稱
public boolean isFull=true;//表示當前緩沖區(qū)中是否有產(chǎn)品
}
//定義消費者類
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("消費者拿出----:" + box.name);
box.isFull = false;//設置緩沖區(qū)中產(chǎn)品為空
box.notify();//發(fā)送通知給生產(chǎn)者線程對象
} else {
try {
//消費者線程進入產(chǎn)品緩沖區(qū)的等待隊列并釋放鎖
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)者線程進入等待隊列并釋放鎖
} 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;//設置緩沖區(qū)中有產(chǎn)品
box.notify();//發(fā)送通知給消費者線程對象
}
}
}
}
}
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)者和消費者對象要共享同一個產(chǎn)品緩沖區(qū)
Thread thread1=new Thread(product);//創(chuàng)建生產(chǎn)者線程對象
Thread thread2=new Thread(cossumer);//創(chuàng)建消費者線程對象
thread1.start();//啟動生產(chǎn)者線程對象
thread2.start();//啟動消費者線程對象
}
}
輸出:
消費者拿出----:香蕉
生產(chǎn)者放入+++++:蘋果
消費者拿出----:蘋果
生產(chǎn)者放入+++++:香蕉
消費者拿出----:香蕉
生產(chǎn)者放入+++++:蘋果
消費者拿出----:蘋果
生產(chǎn)者放入+++++:香蕉
消費者拿出----:香蕉
生產(chǎn)者放入+++++:蘋果
消費者拿出----:蘋果
生產(chǎn)者放入+++++:香蕉
從運行結果可以看出:生產(chǎn)者線程向緩沖區(qū)放入什么產(chǎn)品,消費者就從緩沖區(qū)中取出什么產(chǎn)品,生產(chǎn)者生產(chǎn)一個產(chǎn)品,消費者就消費一個產(chǎn)品,兩者之間實現(xiàn)了通信.
以上就是Java學習之線程同步與線程間通信詳解的詳細內(nèi)容,更多關于Java線程的資料請關注腳本之家其它相關文章!
相關文章
SpringCloud?Gateway詳細分析實現(xiàn)負載均衡與熔斷和限流
這篇文章主要介紹了SpringCloud?Gateway實現(xiàn)路由轉發(fā),負載均衡,熔斷和限流,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
Collection中的size()和isEmpty()區(qū)別說明
這篇文章主要介紹了Collection中的size()和isEmpty()區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
SpringBoot雪花算法主鍵ID傳到前端后精度丟失問題的解決
本文主要介紹了SpringBoot雪花算法主鍵ID傳到前端后精度丟失問題的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08
Spring?Cache注解@Cacheable的九個屬性詳解
在@Cacheable注解的使用中,共有9個屬性供我們來使用,這9個屬性分別是:value、?cacheNames、?key、?keyGenerator、?cacheManager、?cacheResolver、?condition、?unless、?sync,下面介紹@Cacheable注解屬性使用方法,感興趣的朋友一起看看吧2025-05-05

