欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java線程同步及實現(xiàn)方法詳解

 更新時間:2023年11月09日 09:39:12   作者:y_initiate  
這篇文章主要介紹了Java線程同步及實現(xiàn)方法詳解,當我們有多個線程要同時訪問一個變量或?qū)ο髸r,如果這些線程中既有讀又有寫操作時,就會導致變量值或?qū)ο蟮臓顟B(tài)出現(xiàn)混亂,從而導致程序異常,需要的朋友可以參考下

1. 什么是線程同步?

首先,引用一個非常經(jīng)典的例子來說明為什么要進行線程同步

當我們有多個線程要同時訪問一個變量或?qū)ο髸r,如果這些線程中既有讀又有寫操作時,就會導致變量值或?qū)ο蟮臓顟B(tài)出現(xiàn)混亂,從而導致程序異常。 舉個例子,動物園有三個窗口同時在售賣門票,假設還剩最后一張門票時,有兩個窗口同時有人在買門票,此時兩個窗口都觀察到還有一張門票,于是兩個窗口都選擇了賣出,此時門票數(shù)變成了-1,出現(xiàn)錯誤。

還可能會出現(xiàn)其他情況的錯誤,比如剩余10張票時,兩個窗口同時售賣出一張票后修改票數(shù)為9。

package test;
import java.io.*;
public class TicketThreadTest {
    public static void main(String[] args) throws IOException, InterruptedException {
        TicketThread ticket1 = new TicketThread();
        Thread thread1 = new Thread(ticket1, "窗口1");
        Thread thread2 = new Thread(ticket1, "窗口2");
        Thread thread3 = new Thread(ticket1, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
        thread1.join(); // 等待三個線程結(jié)束后打印賣出總數(shù)
        thread2.join();
        thread3.join();
        System.out.println("sellNum: " + TicketThread.sellNum);
    }
}
class TicketThread implements Runnable {
    private static int ticketNum = 20; // 總票數(shù)
    public static int sellNum = 0; // 統(tǒng)計賣出總票數(shù)
    @Override
    public void run() {
        while (true) {
            if (ticketNum > 0) {
                System.out.println(Thread.currentThread().getName() + "賣出了一張票,剩余:" + --ticketNum);  // 賣出一張票
                sellNum++;  // 賣出總票數(shù)加1
            } else {
                break;
            }
            try {
                Thread.sleep(10);  // 每次sleep 10ms,提高出錯可能
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

這是某次程序運行結(jié)果,很顯然賣出29張票,發(fā)生了錯誤

窗口3賣出了一張票,剩余:19
窗口1賣出了一張票,剩余:18
窗口2賣出了一張票,剩余:17
窗口3賣出了一張票,剩余:16
窗口1賣出了一張票,剩余:16
窗口2賣出了一張票,剩余:16
窗口1賣出了一張票,剩余:15
窗口3賣出了一張票,剩余:14
窗口2賣出了一張票,剩余:15
窗口2賣出了一張票,剩余:13
窗口3賣出了一張票,剩余:12
窗口1賣出了一張票,剩余:13
窗口3賣出了一張票,剩余:9
窗口1賣出了一張票,剩余:11
窗口2賣出了一張票,剩余:10
窗口1賣出了一張票,剩余:7
窗口3賣出了一張票,剩余:8
窗口2賣出了一張票,剩余:8
窗口1賣出了一張票,剩余:5
窗口3賣出了一張票,剩余:6
窗口2賣出了一張票,剩余:6
窗口2賣出了一張票,剩余:4
窗口3賣出了一張票,剩余:3
窗口1賣出了一張票,剩余:4
窗口1賣出了一張票,剩余:2
窗口3賣出了一張票,剩余:2
窗口2賣出了一張票,剩余:1
窗口2賣出了一張票,剩余:-1
窗口1賣出了一張票,剩余:0
sellNum: 29

2. Java線程同步方法

Java線程同步有7種方法

  • 使用 synchronized關(guān)鍵字實現(xiàn)線程同步
  • 使用wait和notify實現(xiàn)線程同步
  • 使用特殊域變量(volatile)實現(xiàn)線程同步
  • 使用重入鎖實現(xiàn)線程同步,在JavaSE5.0中新增了一個java.util.concurrent包來支持同步
  • 使用局部變量實現(xiàn)線程同步,如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。
  • 使用阻塞隊列實現(xiàn)線程同步
  • 使用原子變量實現(xiàn)線程同步

3 使用synchronized實現(xiàn)線程同步

synchronized的作用主要有三個:

  • 原子性:確保線程互斥地訪問同步代碼
  • 可見性:保證共享變量的修改能夠及時可見

可見性是通過Java內(nèi)存模型中的“對一個變量unlock操作之前,必須要同步到主內(nèi)存中;如果對一個變量進行l(wèi)ock操作,則將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用此變量前,需要重新從主內(nèi)存中l(wèi)oad操作或assign操作初始化變量值”來保證的

  • 有序性:有效解決重排序問題,即 “一個unlock操作先行發(fā)生(happen-before)于后面對同一個鎖的lock操作”

happen-before:If one action _happens-before _another, then the first is visible to and ordered before the second. 如果指令甲happens-before指令乙,那么指令甲必須排序在指令乙之前,并且指令甲的執(zhí)行結(jié)果對指令乙可見。

3.1 同步代碼塊

同步代碼塊是通過鎖定一個指定的對象,來對同步代碼塊中的代碼進行同步。 一個線程訪問一個對象中的synchronized(this)同步代碼塊時,其他試圖訪問該代碼塊的線程將被阻塞。 注意synchronized必須鎖住的是指定的對象,不同對象間不會阻塞,如果需要鎖住類對象,只需要使用synchronized(Class clazz)鎖住類即可。

我們使用同步代碼塊來解決售票問題

package test;
import java.io.*;
public class TicketThreadTest {
    public static void main(String[] args) throws IOException, InterruptedException {
        TicketThread ticket1 = new TicketThread();
        Thread thread1 = new Thread(ticket1, "窗口1");
        Thread thread2 = new Thread(ticket1, "窗口2");
        Thread thread3 = new Thread(ticket1, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
        thread1.join(); // 等待三個線程結(jié)束后打印賣出總數(shù)
        thread2.join();
        thread3.join();
        System.out.println("sellNum: " + TicketThread.sellNum);
    }
}
class TicketThread implements Runnable {
    private static int ticketNum = 20; // 總票數(shù)
    public static int sellNum = 0; // 統(tǒng)計賣出總票數(shù)
    @Override
    public void run() {
        while (true) {
            synchronized (this) {	// 鎖住對共享變量的訪問
                if (ticketNum > 0) {
                    System.out.println(Thread.currentThread().getName() + "賣出了一張票,剩余:" + --ticketNum);  // 賣出一張票
                    sellNum++;  // 賣出總票數(shù)加1
                } else {
                    break;
                }
            }
            try {
                Thread.sleep(10);  // 每次sleep 10ms,提高出錯可能
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

從運行結(jié)果可以看到synchronized修飾的代碼塊同一時間只能有一個線程訪問

窗口1賣出了一張票,剩余:19
窗口2賣出了一張票,剩余:18
窗口3賣出了一張票,剩余:17
窗口3賣出了一張票,剩余:16
窗口2賣出了一張票,剩余:15
窗口1賣出了一張票,剩余:14
窗口1賣出了一張票,剩余:13
窗口2賣出了一張票,剩余:12
窗口3賣出了一張票,剩余:11
窗口2賣出了一張票,剩余:10
窗口3賣出了一張票,剩余:9
窗口1賣出了一張票,剩余:8
窗口1賣出了一張票,剩余:7
窗口2賣出了一張票,剩余:6
窗口3賣出了一張票,剩余:5
窗口2賣出了一張票,剩余:4
窗口3賣出了一張票,剩余:3
窗口1賣出了一張票,剩余:2
窗口3賣出了一張票,剩余:1
窗口1賣出了一張票,剩余:0
sellNum: 20

注意上述類中的ticketNum和sellNum都屬于類對象,如果我們使用不同的實例對象,使用synchronized(this)鎖住的不是同一個對象,會發(fā)現(xiàn)并沒有實現(xiàn)線程同步,此時就需要鎖住synchronized(this.getClass())。

使用不同實例對象,main方法中修改如下:

//使用不同實例對象,main方法中修改如下:
TicketThread ticket1 = new TicketThread();
TicketThread ticket2 = new TicketThread();
TicketThread ticket3 = new TicketThread();
Thread thread1 = new Thread(ticket1, “窗口1”);
Thread thread2 = new Thread(ticket2, “窗口2”);
Thread thread3 = new Thread(ticket3, “窗口3”);
//TicketThread類中修改為
synchronized(this.getClass())或synchronized(TicketThread.class)

3.2 同步方法

同步方法是對這個方法塊里的代碼進行同步,而這種情況下鎖定的對象就是方法所屬的對象自身。

相當于使用synchronized(this)鎖住方法中的代碼

如果這個方法是靜態(tài)同步方法呢?那么線程鎖定的就不是這個類的對象了,而是這個類對應的java.lang.Class類型的對象。

相當于使用synchronized(this.getClass())鎖住方法中的代碼

**注意:**當一個同步方法或者同步塊被某個線程執(zhí)行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執(zhí)行同步塊,但可以訪問非同步方法中的非同步代碼塊。

上述售票問題使用同步方法實現(xiàn)線程同步

package test;
import java.io.*;
public class TicketThreadTest {
    public static void main(String[] args) throws IOException, InterruptedException {
        TicketThread ticket1 = new TicketThread();
        Thread thread1 = new Thread(ticket1, "窗口1");
        Thread thread2 = new Thread(ticket1, "窗口2");
        Thread thread3 = new Thread(ticket1, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
        thread1.join(); // 等待三個線程結(jié)束后打印賣出總數(shù)
        thread2.join();
        thread3.join();
        System.out.println("sellNum: " + TicketThread.sellNum);
    }
}
class TicketThread implements Runnable {
    private static int ticketNum = 20; // 總票數(shù)
    public static int sellNum = 0; // 統(tǒng)計賣出總票數(shù)
    @Override
    public void run() {
        while (true) {
            if(!sellOneTicket()){
                break;
            }
            try {
                Thread.sleep(10);  // 每次sleep 10ms,提高出錯可能
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    private synchronized boolean sellOneTicket(){
        if (ticketNum > 0) {
            System.out.println(Thread.currentThread().getName() + "賣出了一張票,剩余:" + --ticketNum);  // 賣出一張票
            sellNum++;  // 賣出總票數(shù)加1
            return true;
        } else {
            return false;
        }
    }
}

到此這篇關(guān)于Java線程同步及實現(xiàn)方法詳解的文章就介紹到這了,更多相關(guān)Java線程同步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java五子棋單機版源碼分享

    Java五子棋單機版源碼分享

    這篇文章主要為大家分享了Java五子棋單機版源碼,JavaGUI編寫單機版五子棋,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • SpringBoot應用部署到外置Tomcat的實現(xiàn)

    SpringBoot應用部署到外置Tomcat的實現(xiàn)

    SpringBoot內(nèi)置tomcat使用很方便,本文主要介紹了SpringBoot應用部署到外置Tomcat的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • Feign自定義重試策略及超時時間詳解

    Feign自定義重試策略及超時時間詳解

    這篇文章主要為大家介紹了Feign自定義重試策略及超時時間詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • SpringBoot+隨機鹽值+雙重MD5實現(xiàn)加密登錄

    SpringBoot+隨機鹽值+雙重MD5實現(xiàn)加密登錄

    數(shù)據(jù)加密在很多項目上都可以用到,大部分都會采用MD5進行加密,本文主要介紹了SpringBoot+隨機鹽值+雙重MD5實現(xiàn)加密登錄,具有一定的參考價值,感興趣的可以了解一下
    2024-02-02
  • Java分頁查詢--分頁顯示(實例講解)

    Java分頁查詢--分頁顯示(實例講解)

    下面小編就為大家?guī)硪黄狫ava分頁查詢--分頁顯示(實例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • Java中JVM的雙親委派、內(nèi)存溢出、垃圾回收和調(diào)優(yōu)詳解

    Java中JVM的雙親委派、內(nèi)存溢出、垃圾回收和調(diào)優(yōu)詳解

    這篇文章主要介紹了Java中JVM的雙親委派、內(nèi)存溢出、垃圾回收和調(diào)優(yōu)詳解,類加載器是Java虛擬機(JVM)的一個重要組成部分,它的主要作用是將類的字節(jié)碼加載到內(nèi)存中,并生成對應的Class對象,需要的朋友可以參考下
    2023-07-07
  • SpringBoot集成thymeleaf瀏覽器404的解決方案

    SpringBoot集成thymeleaf瀏覽器404的解決方案

    前后端不分離的古早 SpringMVC 項目通常會使用 thymeleaf 模板引擎來完成 html 頁面與后端接口之間的交互,如果要將項目架構(gòu)升級成 SpringBoot , thymeleaf 也可以照常集成,但有時候會踩到一些坑,所以本文給大家介紹了SpringBoot集成thymeleaf瀏覽器404的解決方案
    2024-12-12
  • spring-boot-thin-launcher插件分離jar包的依賴和配置方式

    spring-boot-thin-launcher插件分離jar包的依賴和配置方式

    這篇文章主要介紹了spring-boot-thin-launcher插件分離jar包的依賴和配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • 客戶端Socket與服務端ServerSocket串聯(lián)實現(xiàn)網(wǎng)絡通信

    客戶端Socket與服務端ServerSocket串聯(lián)實現(xiàn)網(wǎng)絡通信

    這篇文章主要為大家介紹了客戶端Socket與服務端ServerSocket串聯(lián)實現(xiàn)網(wǎng)絡通信的內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • springboot+element-ui實現(xiàn)多文件一次上傳功能

    springboot+element-ui實現(xiàn)多文件一次上傳功能

    這篇文章主要介紹了springboot+element-ui多文件一次上傳功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06

最新評論