Java學(xué)習(xí)之線程同步與線程間通信詳解
線程同步的概念
由于同一個(gè)進(jìn)程的多個(gè)線程共享同一塊存儲(chǔ)空間,在帶來(lái)方便的同時(shí),也會(huì)帶來(lái)訪問(wèn)沖突的問(wèn)題:
舉例:
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)測(cè)線程的狀態(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í)顯然不相符合。
為了解決此問(wèn)題,Java 語(yǔ)言提供專(zhuān)門(mén)的機(jī)制來(lái)避免同一個(gè)對(duì)象被多個(gè)線程同時(shí)訪問(wèn),這個(gè)機(jī)制就是線程同步。
當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問(wèn)同一個(gè)變量,并且有線程需要修改這個(gè)變量時(shí),就必須采用同步的機(jī)制對(duì)其進(jìn)行控制,否則就會(huì)出現(xiàn)邏輯錯(cuò)誤的運(yùn)行結(jié)果

造成上述這種錯(cuò)誤邏輯結(jié)果的原因是:可能有多個(gè)線程取得的是同一個(gè)值,各自修改并存入,從而造成修改慢的后執(zhí)行的線程把執(zhí)行快的線程的修改結(jié)果覆蓋掉了
因?yàn)榫€程在執(zhí)行過(guò)程中不同步,多個(gè)線程在訪問(wèn)同一資源時(shí),需要進(jìn)行同步操作,被訪問(wèn)的資源稱(chēng)為共享資源。
同步的本質(zhì)是加鎖,Java 中的任何一個(gè)對(duì)象都有一把鎖以及和這個(gè)鎖對(duì)應(yīng)的等待隊(duì)列,當(dāng)線程要訪問(wèn)共享資源時(shí),首先要對(duì)相關(guān)的對(duì)象進(jìn)行加鎖
如果加鎖成功,線程對(duì)象才能訪問(wèn)共享資源并且在訪問(wèn)結(jié)束后,要釋放鎖:如果加鎖不成功,那么線程進(jìn)入被加鎖對(duì)象對(duì)應(yīng)的是等待隊(duì)列。
Java用synchronized關(guān)鍵字給針對(duì)共享資源進(jìn)行操作的方法加鎖。每個(gè)鎖只有一把鑰匙,只有得到這把鑰匙之后才可以對(duì)被保護(hù)的資源進(jìn)行操作,而其他線程只能等待,直到拿到這把鑰匙。
實(shí)現(xiàn)同步的具體方式有同步代碼塊和同步方法兩種
同步代碼塊
使用 synchronized 關(guān)鍵字聲明的代碼塊稱(chēng)為同步代碼塊。
在任意時(shí)刻,只能有一個(gè)線程訪問(wèn)同步代碼塊中的代碼,所以同步代碼塊也稱(chēng)為互斥代碼塊
同步代碼塊格式如下所示:
synchronized(同步對(duì)象){
//需要同步的代碼,對(duì)共享資源的訪問(wèn)
}
synchronized關(guān)鍵字后面括號(hào)內(nèi)的對(duì)象就是被加載的對(duì)象,同步代碼塊要實(shí)現(xiàn)對(duì)共享資源的訪問(wèn)
對(duì)上述實(shí)例進(jìn)行修改:
package Runnable;
public class Runnable_test implements Runnable {//實(shí)現(xiàn)Runnable接口
private int ticknumbers = 20;
private Object obj = new Object();//被加鎖的對(duì)象,同步對(duì)象
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticknumbers > 0) {
System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()監(jiān)測(cè)線程的狀態(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)生變化的代碼塊修改為同步代碼塊:

修改過(guò)后輸出,我們發(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í),在抽票的過(guò)程中通過(guò)給同一個(gè) obj對(duì)象加鎖來(lái)實(shí)現(xiàn)互斥,從而保證線程的同步執(zhí)行。
同步方法
synchronized關(guān)鍵字也可以出現(xiàn)在方法的聲明部分,該方法稱(chēng)為同步方法
當(dāng)多個(gè)線程對(duì)象同時(shí)訪問(wèn)共享資源時(shí),只有獲得鎖對(duì)象的線程才能進(jìn)入同步方法執(zhí)行,其他訪問(wèn)共享資源的線程將會(huì)進(jìn)入鎖對(duì)象的等待隊(duì)列,執(zhí)行完同步方法的線程會(huì)釋放鎖。
[權(quán)限訪問(wèn)限定] synchronized 方法返回值 方法名稱(chēng)(參數(shù)列表){
//.............需要同步的代碼,對(duì)共享資源的訪問(wèn)
}
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();
}
}
}
}
//測(cè)試類(lèi)
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ì)也是給對(duì)象加鎖,但是是給同步方法所在類(lèi)的 this 對(duì)象加鎖,所以在上述實(shí)例中,我們就刪除了obj對(duì)象的定義。
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)測(cè)線程的狀態(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;
}
}
//測(cè)試類(lèi)
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票
通過(guò)程序運(yùn)行結(jié)果可以看出:線程thread1執(zhí)行同步代碼塊,線程thread2執(zhí)行同步方法,兩個(gè)線程之間形成了同步。
由于同步代碼塊是給 this對(duì)象加鎖,所以表明同步方法也是給 this對(duì)象加鎖,否則,兩者之間不能形成同步。
注意:多線程的同步程序中,不同的線程對(duì)象必須給同一個(gè)對(duì)象加鎖,否則這些線程對(duì)象之間無(wú)法實(shí)現(xiàn)同步
線程組
線程組可以看作是包含了許多線程的對(duì)象集,它擁有一個(gè)名字以及一些相關(guān)的屬性,可以當(dāng)作一個(gè)組來(lái)管理其中的線程。
每個(gè)線程都是線程組的一個(gè)成員,線程組把多個(gè)線程集成一個(gè)對(duì)象,通過(guò)線程組可以同時(shí)對(duì)其中的多個(gè)線程進(jìn)行操作。在生成線程時(shí)必須將線程放到指定的線程組,也可以放在缺省的線程組中,缺省的就是生成該線程的線程所在的線程組。一旦一個(gè)線程加入了某個(gè)線程組,就不能被移出這個(gè)組。
java,lang包的ThreadGroup類(lèi)表示線程組,在創(chuàng)建線程之前,可以創(chuàng)建一個(gè)ThreadGroup對(duì)象。
下面代碼是創(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)先級(jí),pri是該線程組的新優(yōu)先級(jí) void interrupt() //向線程組及其子組中的線程發(fā)送一個(gè)中斷信息 boolean isDaemon() //判斷是否為Daemon線程組 boolean parentOf(ThreadGoup g) //判斷線程組是否是線程g或g的子線程 toString() //返回一個(gè)表示本線程組的字符串
線程組對(duì)象的基本應(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)先級(jí)
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)先級(jí):"+A.getPriority());
System.out.println("線程B的優(yōu)先級(jí):"+B.getPriority());
System.out.println("線程C的優(yōu)先級(jí):"+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)先級(jí):6
線程B的優(yōu)先級(jí):5
線程C的優(yōu)先級(jí):4
線程間的通信
某些情況下,多個(gè)線程之間需要相互配合來(lái)完成一件事情,這些線程之間就需要進(jìn)行通信”,把一方線程的執(zhí)行情況告訴給另一方線程。
“通信”的方法在 java.lang.Object類(lèi)中定義了,我們可以通過(guò)“生產(chǎn)者-消費(fèi)者”模型來(lái)理解線程間的通信。
有兩個(gè)線程對(duì)象,其中一個(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ì)象的等待隊(duì)列并釋放產(chǎn)品緩沖區(qū)對(duì)象的鎖;如果發(fā)現(xiàn)產(chǎn)品緩沖區(qū)中沒(méi)有產(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)品就拿出來(lái)消費(fèi)并調(diào)用 notify()方法發(fā)送通知給生產(chǎn)者線程;如果發(fā)現(xiàn)產(chǎn)品緩沖區(qū)中沒(méi)有產(chǎn)品,調(diào)用 wait()方法進(jìn)入產(chǎn)品緩沖區(qū)對(duì)象的等待隊(duì)列并釋放產(chǎn)品緩沖區(qū)對(duì)象的鎖。
注意:線程間通信是建立在線程同步基礎(chǔ)上的,所以wait()notify()和notifyAll()方法的調(diào)用要出現(xiàn)在同步代碼塊或同步方法中
線程通信簡(jiǎn)單應(yīng)用
package Runnable;
class Box {//產(chǎn)品緩沖區(qū)
public String name="蘋(píng)果";//表示產(chǎn)品的名稱(chēng)
public boolean isFull=true;//表示當(dāng)前緩沖區(qū)中是否有產(chǎn)品
}
//定義消費(fèi)者類(lèi)
class Cossumer implements Runnable {
Box box;
Cossumer(Box box) {
this.box = box;
}
@Override
public void run() {
while (true) {
synchronized (box) {//對(duì)產(chǎn)品緩沖區(qū)對(duì)象加鎖
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)者線程對(duì)象
} else {
try {
//消費(fèi)者線程進(jìn)入產(chǎn)品緩沖區(qū)的等待隊(duì)列并釋放鎖
box.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//生產(chǎn)者類(lèi)
class product implements Runnable{
Box box;
int Count=0;
public product(Box box) {
this.box=box;
}
@Override
public void run() {
while(true){
synchronized (box)//對(duì)產(chǎn)品緩沖區(qū)對(duì)象加鎖
{
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 = "蘋(píng)果";
System.out.println("生產(chǎn)者放入+++++:" + box.name);
}
Count=(Count+1)%2;
box.isFull=true;//設(shè)置緩沖區(qū)中有產(chǎn)品
box.notify();//發(fā)送通知給消費(fèi)者線程對(duì)象
}
}
}
}
}
class box_test{
public static void main(String[] args) {
Box box=new Box();//創(chuàng)建產(chǎn)品緩沖區(qū)對(duì)象
product product=new product(box);
Cossumer cossumer=new Cossumer(box);//生產(chǎn)者和消費(fèi)者對(duì)象要共享同一個(gè)產(chǎn)品緩沖區(qū)
Thread thread1=new Thread(product);//創(chuàng)建生產(chǎn)者線程對(duì)象
Thread thread2=new Thread(cossumer);//創(chuàng)建消費(fèi)者線程對(duì)象
thread1.start();//啟動(dòng)生產(chǎn)者線程對(duì)象
thread2.start();//啟動(dòng)消費(fèi)者線程對(duì)象
}
}
輸出:
消費(fèi)者拿出----:香蕉
生產(chǎn)者放入+++++:蘋(píng)果
消費(fèi)者拿出----:蘋(píng)果
生產(chǎn)者放入+++++:香蕉
消費(fèi)者拿出----:香蕉
生產(chǎn)者放入+++++:蘋(píng)果
消費(fèi)者拿出----:蘋(píng)果
生產(chǎn)者放入+++++:香蕉
消費(fèi)者拿出----:香蕉
生產(chǎn)者放入+++++:蘋(píng)果
消費(fèi)者拿出----:蘋(píng)果
生產(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線程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java開(kāi)發(fā)BeanUtils類(lèi)解決實(shí)體對(duì)象間賦值
這篇文章主要為大家介紹了java開(kāi)發(fā)中使用BeanUtils類(lèi)實(shí)現(xiàn)實(shí)體對(duì)象之間的賦值有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步學(xué)有所得2021-10-10
SpringCloud?Gateway詳細(xì)分析實(shí)現(xiàn)負(fù)載均衡與熔斷和限流
這篇文章主要介紹了SpringCloud?Gateway實(shí)現(xiàn)路由轉(zhuǎn)發(fā),負(fù)載均衡,熔斷和限流,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
Collection中的size()和isEmpty()區(qū)別說(shuō)明
這篇文章主要介紹了Collection中的size()和isEmpty()區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Mybatis一對(duì)多與多對(duì)一查詢(xún)處理詳解
這篇文章主要給大家介紹了關(guān)于Mybatis一對(duì)多與多對(duì)一查詢(xún)處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Java去掉小數(shù)點(diǎn)后面無(wú)效0的方案與建議
當(dāng)前小數(shù)點(diǎn)后面的位數(shù)過(guò)多的時(shí)候,多余的0沒(méi)有實(shí)際意義,下面這篇文章主要給大家介紹了關(guān)于Java去掉小數(shù)點(diǎn)后面無(wú)效0的方案與建議,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
SpringBoot雪花算法主鍵ID傳到前端后精度丟失問(wèn)題的解決
本文主要介紹了SpringBoot雪花算法主鍵ID傳到前端后精度丟失問(wèn)題的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Mybatis控制臺(tái)打印Sql語(yǔ)句的實(shí)現(xiàn)代碼
MyBatis是一個(gè)支持普通SQL查詢(xún),存儲(chǔ)過(guò)程和高級(jí)映射的優(yōu)秀持久層框架,下面給大家介紹Mybatis控制臺(tái)打印Sql語(yǔ)句的實(shí)現(xiàn)代碼,非常不錯(cuò),感興趣的朋友一起看下吧2016-07-07
Spring?Cache注解@Cacheable的九個(gè)屬性詳解
在@Cacheable注解的使用中,共有9個(gè)屬性供我們來(lái)使用,這9個(gè)屬性分別是:value、?cacheNames、?key、?keyGenerator、?cacheManager、?cacheResolver、?condition、?unless、?sync,下面介紹@Cacheable注解屬性使用方法,感興趣的朋友一起看看吧2025-05-05

