最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 多線程的同步代碼塊詳解

 更新時(shí)間:2021年10月28日 15:41:43   作者:一紙春秋  
這篇文章主要介紹了Java 多線程的同步代碼塊,使用synchronized關(guān)鍵字創(chuàng)建線程同步方法是實(shí)現(xiàn)線程同步的關(guān)鍵,需要的朋友可以參考下

火車(chē)站搶票問(wèn)題

由于現(xiàn)實(shí)中買(mǎi)票也不會(huì)是零延遲的,為了真實(shí)性加入了延遲機(jī)制,也就是線程休眠語(yǔ)句

package test.MyThread.ticketDemo;
public class RunnableThread implements Runnable{
    private int ticket = 100;
    @Override
    public void run(){
        while(true){
            if(ticket>0){
                try {
                    Thread.sleep(100);  //語(yǔ)句一
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售第 "+ticket+" 張票");  //語(yǔ)句二
                ticket--;  //語(yǔ)句三
            }
        }
    }
}
package test.MyThread.ticketDemo;
public class ticketDemo1 {
    public static void main(String[] args) {
        RunnableThread r1 = new RunnableThread();
        Thread t1 = new Thread(r1,"窗口一");
        Thread t2 = new Thread(r1,"窗口二");
        Thread t3 = new Thread(r1,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

在這里插入圖片描述

但是結(jié)果和我們想象中的不一樣,三個(gè)窗口賣(mài)出了同樣的票

這是因?yàn)?,CPU的操作具有原子性,單獨(dú)執(zhí)行一條指令或者說(shuō)語(yǔ)句,在執(zhí)行完畢前不會(huì)被中斷。

三個(gè)線程被啟動(dòng)后,都會(huì)處于就緒狀態(tài),然后開(kāi)始搶奪CPU執(zhí)行語(yǔ)句。

1.語(yǔ)句一:Thread.sleep(100);

2.語(yǔ)句二: System.out.println(Thread.currentThread().getName()+“正在出售第 “+ticket+” 張票”);

3.語(yǔ)句三: ticket–;

我將程序中需要執(zhí)行的三條主要語(yǔ)句列了出來(lái)

三條線程中,加入線程一先搶到了CPU,這時(shí)就會(huì)開(kāi)始執(zhí)行語(yǔ)句,也就是至少會(huì)完成一條語(yǔ)句一,然后進(jìn)入休眠。

注:如果語(yǔ)句一不是休眠語(yǔ)句,而是別的語(yǔ)句,那么線程一就可以繼續(xù)往下執(zhí)行,因?yàn)樵有?,正在?zhí)行的語(yǔ)句不會(huì)被打斷,所以只會(huì)在一條語(yǔ)句結(jié)束,下一條語(yǔ)句未開(kāi)始時(shí),被搶走CPU或者中斷,導(dǎo)致線程退出運(yùn)行狀態(tài),轉(zhuǎn)為就緒或者阻塞狀態(tài)。所以線程一可以一次性完成多條語(yǔ)句,也有可能剛完成一條語(yǔ)句就被搶走了CPU。

接著,線程二,線程三也搶到了CPU,也開(kāi)始執(zhí)行語(yǔ)句一,然后也進(jìn)入休眠狀態(tài)。之后線程一二三從休眠中醒來(lái),開(kāi)始爭(zhēng)搶CPU完成語(yǔ)句二,但是三者都在完成語(yǔ)句三之前被搶走了CPU,導(dǎo)致一直沒(méi)有執(zhí)行ticket–語(yǔ)句,ticket也就沒(méi)有減少,因此三條線程一共打印三條輸出語(yǔ)句,里面的ticket都是相同。

然后三條線程又開(kāi)始爭(zhēng)搶CPU來(lái)完成語(yǔ)句三,一個(gè)線程讓ticket減一,三個(gè)線程減少三張票。完成語(yǔ)句三后,又開(kāi)始新的循環(huán),三個(gè)線程開(kāi)始爭(zhēng)搶CPU完成語(yǔ)句一。

因此,看到的結(jié)果會(huì)是,三條語(yǔ)句的ticket都相同,然后ticket突然減三,接著又輸出三條ticket相同的輸出語(yǔ)句。

那么,該如何解決這種情況呢?

這種延遲賣(mài)票的問(wèn)題被稱(chēng)為線程安全問(wèn)題,要發(fā)生線程安全問(wèn)題需要滿(mǎn)足三個(gè)條件(任何一共條件不滿(mǎn)足都不會(huì)造成線程安全問(wèn)題):

1.是否存在多線程環(huán)境

2.是否存在共享數(shù)據(jù)/共享變量

3.是否有多條語(yǔ)句操作著共享數(shù)據(jù)/共享變量

火車(chē)站延遲賣(mài)票問(wèn)題滿(mǎn)足這三個(gè)條件,因此造成了線程安全問(wèn)題,而前兩條都不可避免,那么就可以著手于破壞掉第三個(gè)條件,讓線程安全問(wèn)題不成立。

思路是將多條語(yǔ)句包裝成一個(gè)同步代碼塊,當(dāng)某個(gè)線程執(zhí)行這個(gè)同步代碼塊的時(shí)候,就跟原子性一樣,其他的線程不能搶占CPU,只能等這個(gè)同步代碼塊執(zhí)行完畢。

解決辦法:

1.synchronized —— 自動(dòng)鎖

2.lock —— 手動(dòng)鎖

synchronized

synchronized(對(duì)象){  
	//可能會(huì)發(fā)生線程安全問(wèn)題的代碼
}
//這里的對(duì)象可以是任意對(duì)象,我們可以用 Object obj =  new Object()里面的obj放入括號(hào)中

使用synchronized的條件:

1.必須有兩個(gè)或兩個(gè)以上的線程同一時(shí)間只有一個(gè)線程能夠執(zhí)行同步代碼塊多個(gè)線程想要同步時(shí),必須共用同一把鎖
synchronized(對(duì)象)括號(hào)里面的對(duì)象就是一把鎖

使用synchronized的過(guò)程:

1.只有搶到鎖的線程才可以執(zhí)行同步代碼塊,其余的線程即使搶到了CPU執(zhí)行權(quán),也只能等待,等待鎖的釋放。

2.代碼執(zhí)行完畢或者程序拋出異常都會(huì)釋放鎖,然后還未執(zhí)行同步代碼塊的線程爭(zhēng)搶鎖,誰(shuí)搶到誰(shuí)就能運(yùn)行同步代碼塊。

同步代碼塊

因此,修改后的代碼為:

package test.MyThread.ticketDemo;
public class RunnableThread implements Runnable{
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run(){
        while(true){
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第 " + ticket + " 張票");
                    ticket--;
                }
            }
        }
    }
}
package test.MyThread.ticketDemo;
public class ticketDemo1 {
    public static void main(String[] args) {
    //這里沒(méi)有改動(dòng),只是在上一個(gè)代碼中加了一把鎖
        RunnableThread r1 = new RunnableThread();
        Thread t1 = new Thread(r1,"窗口一");
        Thread t2 = new Thread(r1,"窗口二");
        Thread t3 = new Thread(r1,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

在這里插入圖片描述

可以看出來(lái)結(jié)果符合我們的預(yù)期,是正確的

現(xiàn)在又有了新的問(wèn)題,那就是如果我在構(gòu)造線程的RunnableThread類(lèi)里面加入方法呢?同步代碼塊里面出現(xiàn)方法時(shí),我們應(yīng)該怎么“上鎖”呢?

同步方法(this鎖)

同步方法,在public的后面加上synchronized關(guān)鍵字

package test.MyThread.ticketDemo;
public class RunnableThread1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();
    public boolean flag = true;
    @Override
    public void run(){
        if(flag==true){
            while(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                SellTicket1();
            }
        }
    }
//同步方法,在public的后面加上synchronized關(guān)鍵字
    public synchronized void SellTicket1(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在出售第 "+ticket+" 張票");
            ticket--;
        }
    }
}
package test.MyThread.ticketDemo;
public class ticketDemo2 {
    public static void main(String[] args) throws InterruptedException {
        RunnableThread1 r = new RunnableThread1();
        Thread t1 = new Thread(r,"窗口一");
        Thread t2 = new Thread(r,"窗口二");
        t1.start();
        t2.start();
    }
}

this鎖

先來(lái)看看,如果有兩條路徑,一條路徑是使用同步代碼塊,但是對(duì)象是obj,另一條路徑是使用同步方法

package test.MyThread.ticketDemo;
public class TicketWindow2 implements Runnable{
    //定義100張票
    private static int tickets = 100;
    Object obj = new Object();
    int i =0;
    @Override
    public void run() {
        while (true){
            if(i%2==0){
                synchronized (obj){
                    if(tickets>0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 張票");
                    }
                }
            }else {
                sellTicket();
            }
            i++;
        }
    }
    public synchronized void sellTicket(){
            if(tickets>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 張票");
            }
    }
}

在這里插入圖片描述

結(jié)果出錯(cuò),說(shuō)明同步方法的用的對(duì)象鎖不能是任意的對(duì)象,不同的線程應(yīng)該用相同的鎖。同步方法是屬于對(duì)象,而在這個(gè)類(lèi)里面調(diào)用方法的是this對(duì)象,也就是this.sellTicket(),因此把this提取出來(lái)作為對(duì)象鎖中的對(duì)象。這樣多個(gè)線程都用的是this鎖

package test.MyThread.ticketDemo;
public class TicketWindow2 implements Runnable{
    //定義100張票
    private static int tickets = 100;
    Object obj = new Object();
    int i =0;
    @Override
    public void run() {
        while (true){
            if(i%2==0){
                synchronized (this){
                    if(tickets>0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 張票");
                    }
                }
            }else {
                sellTicket();
            }
            i++;
        }
    }
    public synchronized void sellTicket(){
            if(tickets>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 張票");
            }
    }
}

修改完成后再運(yùn)行代碼,發(fā)現(xiàn)沒(méi)有錯(cuò)誤

注:

1.一個(gè)線程使用同步方法,另一個(gè)線程使用同步代碼塊this鎖,可以實(shí)現(xiàn)同步

2.一個(gè)線程使用同步方法,另一個(gè)線程使用同步代碼塊,但是不是this鎖。這種情況不能實(shí)現(xiàn)同步。

靜態(tài)同步方法

同步方法的鎖對(duì)象是this,

靜態(tài)同步方法的鎖對(duì)象是:這個(gè)靜態(tài)同步方法所屬的類(lèi)的字節(jié)碼文件

下面代碼挺長(zhǎng)的,但其實(shí)就修改了上面同步方法的代碼的兩處地方

1.public synchronized void sellTicket(){}改為
public synchronized static void sellTicket(){}

2.synchronized (this){}改為synchronized (TicketWindow2.class){}

package test.MyThread.ticketDemo;
public class TicketWindow2 implements Runnable{
    //定義100張票
    private static int tickets = 100;
    Object obj = new Object();
    int i =0;
    @Override
    public void run() {
        while (true){
            if(i%2==0){
                synchronized (TicketWindow2.class){
                    if(tickets>0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 張票");
                    }
                }
            }else {
                sellTicket();
            }
            i++;
        }
    }
    public synchronized static void sellTicket(){
        if(tickets>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 張票");
        }
    }
}

main()方法里面創(chuàng)建進(jìn)程和啟動(dòng)進(jìn)程的代碼,和上面同步方法里面的代碼相同

結(jié)果也和上面的一樣,都不再列出來(lái)了

死鎖問(wèn)題

package test.MyThread.ticketDemo;
//兩個(gè)不同的鎖對(duì)象
public class LockObject {
    public static final Object lock1 = new Object();
    public static final Object lock2 = new Object();
}
package test.MyThread.ticketDemo;
public class DieLockThread extends Thread{
    public boolean flag;
    public DieLockThread(boolean flag){
        this.flag = flag;
    }
    @Override
    public void run() {
        if(flag){
            synchronized(LockObject.lock1){
                System.out.println("lock1");
                synchronized(LockObject.lock2){
                    System.out.println("lock2");
                }
            }
        }else{
            synchronized(LockObject.lock2){
                System.out.println("lock2");
                synchronized(LockObject.lock1){
                    System.out.println("lock1");
                }
            }
        }
    }
}
package test.MyThread.ticketDemo;
public class DieLockDemo {
    public static void main(String[] args) {
        DieLockThread d1 = new DieLockThread(true);
        DieLockThread d2 = new DieLockThread(false);
        d1.start();
        d2.start();
    }
}

在這里插入圖片描述

程序會(huì)卡在這一步,不能進(jìn)行下一步也不能停止

利用有參構(gòu)造,構(gòu)造出來(lái)的線程d1應(yīng)該是先獲得鎖對(duì)象LockObject.lock1然后執(zhí)行打印語(yǔ)句。接著獲取鎖對(duì)象LockObject.lock2,然后打印lock2。

但是這里因?yàn)榫€程d2是先獲取的鎖對(duì)象LockObject.lock2,并占據(jù)這個(gè)鎖對(duì)象,然后想獲得鎖對(duì)象LockObject.lock1,但LockObject.lock1此時(shí)被線程d1占據(jù)著

兩個(gè)線程都在等待對(duì)方釋放鎖對(duì)象,然后進(jìn)行下一步,但是兩者都不釋放,導(dǎo)致程序卡死在這里。這就造成了死鎖。

lock

package test.MyThread.ticketDemo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockThread implements Runnable{
    private int ticket = 100;
    Lock lock = new ReentrantLock();
    @Override
    public void run(){
        while(ticket>0){
            try{
                lock.lock();
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 正在出售第 " + (ticket--) + " 張票");
                }
            }finally {
                lock.unlock();
            }
        }
    }
}
package test.MyThread.ticketDemo;
public class LockDemo {
    public static void main(String[] args) {
        LockThread lt = new LockThread();
        Thread t1 = new Thread(lt,"窗口一");
        Thread t2 = new Thread(lt,"窗口二");
        Thread t3 = new Thread(lt,"窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

在這里插入圖片描述

結(jié)果正確

總結(jié)

本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • Java不可不知的泛型使用示例代碼

    Java不可不知的泛型使用示例代碼

    這篇文章主要介紹了Java不可不知的泛型使用,本文通過(guò)實(shí)例代碼給大家介紹了java的泛型的基本使用,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Java并發(fā)編程之LongAdder執(zhí)行情況解析

    Java并發(fā)編程之LongAdder執(zhí)行情況解析

    這篇文章主要為大家介紹了Java并發(fā)編程之LongAdder執(zhí)行情況解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • Spring Cloud Config工作原理概述

    Spring Cloud Config工作原理概述

    Spring Cloud Config 是 Spring Cloud 生態(tài)系統(tǒng)的一部分,它提供了一種集中化管理應(yīng)用配置的方法,本文給大家介紹Spring Cloud Config工作原理概述,感興趣的朋友跟隨小編一起看看吧
    2024-08-08
  • Spring IOC的三種實(shí)現(xiàn)方式詳解

    Spring IOC的三種實(shí)現(xiàn)方式詳解

    這篇文章主要介紹了Spring IOC的三種實(shí)現(xiàn)方式,在Spring框架中,IOC通過(guò)依賴(lài)注入來(lái)實(shí)現(xiàn),而依賴(lài)注入主要有三種實(shí)現(xiàn)方式,構(gòu)造器注入、Setter注入和字段注入,每種方式都有其特點(diǎn)、適用場(chǎng)景和優(yōu)缺點(diǎn),需要的朋友可以參考下
    2025-02-02
  • idea中方法、注釋、導(dǎo)入類(lèi)折疊或是展開(kāi)的設(shè)置方法

    idea中方法、注釋、導(dǎo)入類(lèi)折疊或是展開(kāi)的設(shè)置方法

    這篇文章主要介紹了idea中方法、注釋、導(dǎo)入類(lèi)折疊或是展開(kāi)的設(shè)置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • Java使用Ajax異步上傳文件

    Java使用Ajax異步上傳文件

    使用Ajax上傳文件的應(yīng)用場(chǎng)景頗多,比如上傳用戶(hù)頭像、博客文章中插入圖片、對(duì)認(rèn)證用戶(hù)相關(guān)身份進(jìn)行校驗(yàn)等等很多很多。本文提供一個(gè)簡(jiǎn)單的示例供大家參考
    2021-05-05
  • SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出

    SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出

    這篇文章主要介紹了SpringBoot中EasyExcel實(shí)現(xiàn)Excel文件的導(dǎo)入導(dǎo)出,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • jvm中指定時(shí)區(qū)信息user.timezone問(wèn)題及解決方式

    jvm中指定時(shí)區(qū)信息user.timezone問(wèn)題及解決方式

    同一份程序使用時(shí)間LocalDateTime類(lèi)型,在國(guó)內(nèi)和國(guó)外部署后,返回的時(shí)間信息前端使用出問(wèn)題,這篇文章主要介紹了jvm中指定時(shí)區(qū)信息user.timezone問(wèn)題及解決方法,需要的朋友可以參考下
    2023-02-02
  • java內(nèi)存模型jvm虛擬機(jī)簡(jiǎn)要分析

    java內(nèi)存模型jvm虛擬機(jī)簡(jiǎn)要分析

    Java 內(nèi)存模型的主要目的是定義程序中各種變量的訪問(wèn)規(guī)則, 關(guān)注在虛擬機(jī)中把變量值存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量值這樣的底層細(xì)節(jié)
    2021-09-09
  • 關(guān)于Java 項(xiàng)目封裝sqlite連接池操作持久化數(shù)據(jù)的方法

    關(guān)于Java 項(xiàng)目封裝sqlite連接池操作持久化數(shù)據(jù)的方法

    這篇文章主要介紹了Java 項(xiàng)目封裝sqlite連接池操作持久化數(shù)據(jù)的方法,文中給大家介紹了sqlite的體系結(jié)構(gòu)及封裝java的sqlite連接池的詳細(xì)過(guò)程,需要的朋友可以參考下
    2021-11-11

最新評(píng)論

乐昌市| 丰县| 民勤县| 土默特左旗| 色达县| 加查县| 兴宁市| 浙江省| 项城市| 西藏| 东方市| 金华市| 罗源县| 元江| 商南县| 阿巴嘎旗| 保靖县| 凤台县| 新津县| 奉化市| 龙胜| 靖安县| 剑河县| 泽库县| 梨树县| 湾仔区| 五常市| 望谟县| 双柏县| 临邑县| 龙南县| 屏边| 慈利县| 寻乌县| 天水市| 临沭县| 连城县| 远安县| 高邮市| 基隆市| 塔城市|