亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

1. 什么是線程同步?

首先,引用一個(gè)非常經(jīng)典的例子來(lái)說(shuō)明為什么要進(jìn)行線程同步

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

還可能會(huì)出現(xiàn)其他情況的錯(cuò)誤,比如剩余10張票時(shí),兩個(gè)窗口同時(shí)售賣(mài)出一張票后修改票數(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(); // 等待三個(gè)線程結(jié)束后打印賣(mài)出總數(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)計(jì)賣(mài)出總票數(shù)
    @Override
    public void run() {
        while (true) {
            if (ticketNum > 0) {
                System.out.println(Thread.currentThread().getName() + "賣(mài)出了一張票,剩余:" + --ticketNum);  // 賣(mài)出一張票
                sellNum++;  // 賣(mài)出總票數(shù)加1
            } else {
                break;
            }
            try {
                Thread.sleep(10);  // 每次sleep 10ms,提高出錯(cuò)可能
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

這是某次程序運(yùn)行結(jié)果,很顯然賣(mài)出29張票,發(fā)生了錯(cuò)誤

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

2. Java線程同步方法

Java線程同步有7種方法

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

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

synchronized的作用主要有三個(gè):

  • 原子性:確保線程互斥地訪問(wèn)同步代碼
  • 可見(jiàn)性:保證共享變量的修改能夠及時(shí)可見(jiàn)

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

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

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

3.1 同步代碼塊

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

我們使用同步代碼塊來(lái)解決售票問(wè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(); // 等待三個(gè)線程結(jié)束后打印賣(mài)出總數(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)計(jì)賣(mài)出總票數(shù)
    @Override
    public void run() {
        while (true) {
            synchronized (this) {	// 鎖住對(duì)共享變量的訪問(wèn)
                if (ticketNum > 0) {
                    System.out.println(Thread.currentThread().getName() + "賣(mài)出了一張票,剩余:" + --ticketNum);  // 賣(mài)出一張票
                    sellNum++;  // 賣(mài)出總票數(shù)加1
                } else {
                    break;
                }
            }
            try {
                Thread.sleep(10);  // 每次sleep 10ms,提高出錯(cuò)可能
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

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

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

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

使用不同實(shí)例對(duì)象,main方法中修改如下:

//使用不同實(shí)例對(duì)象,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類(lèi)中修改為
synchronized(this.getClass())或synchronized(TicketThread.class)

3.2 同步方法

同步方法是對(duì)這個(gè)方法塊里的代碼進(jìn)行同步,而這種情況下鎖定的對(duì)象就是方法所屬的對(duì)象自身。

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

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

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

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

上述售票問(wèn)題使用同步方法實(shí)現(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(); // 等待三個(gè)線程結(jié)束后打印賣(mài)出總數(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)計(jì)賣(mài)出總票數(shù)
    @Override
    public void run() {
        while (true) {
            if(!sellOneTicket()){
                break;
            }
            try {
                Thread.sleep(10);  // 每次sleep 10ms,提高出錯(cuò)可能
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
    private synchronized boolean sellOneTicket(){
        if (ticketNum > 0) {
            System.out.println(Thread.currentThread().getName() + "賣(mài)出了一張票,剩余:" + --ticketNum);  // 賣(mài)出一張票
            sellNum++;  // 賣(mài)出總票數(shù)加1
            return true;
        } else {
            return false;
        }
    }
}

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

相關(guān)文章

最新評(píng)論