Java 中使用同步線程的方式

1. 前序

在多線程編程中长已,線程同步是確保數(shù)據(jù)一致性和防止競(jìng)態(tài)條件的關(guān)鍵资锰。Java 提供了多種用于線程同步的機(jī)制髓梅,以解決不同場(chǎng)景下的線程競(jìng)爭(zhēng)問(wèn)題。無(wú)論是最基本的 synchronized 關(guān)鍵字便瑟,還是更靈活的 ReentrantLockReentrantReadWriteLock憨颠,它們都為開(kāi)發(fā)者提供了不同級(jí)別的鎖和控制胳徽。

本文將逐一介紹 Java 中常見(jiàn)的同步機(jī)制,涵蓋了 synchronized爽彤、ReentrantLock养盗、Atomic 類等,同時(shí)給出每種機(jī)制的示例代碼和適用場(chǎng)景适篙,幫助你更好地理解并應(yīng)用這些同步機(jī)制往核。

接下來(lái),我們將詳細(xì)討論并演示每種方式的使用方法嚷节。

2. synchronized

2.1 synchronized 關(guān)鍵字

  • 描述:Java 中最基礎(chǔ)的同步機(jī)制就是 synchronized 關(guān)鍵字聂儒,它可以用于方法或代碼塊,確保同一時(shí)刻只有一個(gè)線程可以訪問(wèn)共享資源硫痰。
  • 示例代碼
/**
 * 示例類衩婚,演示如何使用 synchronized 方法進(jìn)行線程同步
 */
public class SynchronizedMethodExample {
    /** 共享計(jì)數(shù)器 */
    private int counter = 0;

    /**
     * 同步遞增計(jì)數(shù)器的方法,確保同一時(shí)刻只有一個(gè)線程可以執(zhí)行
     */
    public synchronized void increment() {
        // 遞增計(jì)數(shù)器
        counter++;
        // 輸出當(dāng)前線程和計(jì)數(shù)器的值
        System.out.println(Thread.currentThread().getName() + " - Counter: " + counter);
    }

    /**
     * 主程序入口效斑,創(chuàng)建多個(gè)線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)
     */
    public static void main(String[] args) {
        SynchronizedMethodExample example = new SynchronizedMethodExample();

        // 線程任務(wù)非春,調(diào)用 increment 方法
        Runnable task = example::increment;

        // 創(chuàng)建兩個(gè)線程
        Thread t1 = new Thread(task, "Thread 1");
        Thread t2 = new Thread(task, "Thread 2");

        // 啟動(dòng)線程
        t1.start();
        t2.start();
    }
}

2.2 使用 synchronized 代碼塊

  • 描述:相比于 synchronized 方法,synchronized 代碼塊允許更細(xì)粒度地控制同步范圍缓屠∑骊迹可以指定特定的代碼塊進(jìn)行同步,而不是整個(gè)方法敌完,這樣可以減少鎖的競(jìng)爭(zhēng)储耐,提高效率。
  • 示例代碼
/**
 * 示例類滨溉,演示如何使用 synchronized 代碼塊進(jìn)行線程同步
 */
public class SynchronizedBlockExample {
    /** 共享計(jì)數(shù)器 */
    private int counter = 0;
    /** 自定義鎖對(duì)象 */
    private final Object lock = new Object();

    /**
     * 同步遞增計(jì)數(shù)器的方法什湘,只鎖定代碼塊
     */
    public void increment() {
        // 使用 synchronized 代碼塊確保鎖定的粒度更小
        synchronized (lock) {
            counter++;
            System.out.println(Thread.currentThread().getName() + " - Counter: " + counter);
        }
    }

    /**
     * 主程序入口长赞,創(chuàng)建多個(gè)線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)
     */
    public static void main(String[] args) {
        SynchronizedBlockExample example = new SynchronizedBlockExample();

        Runnable task = example::increment;

        Thread t1 = new Thread(task, "Thread 1");
        Thread t2 = new Thread(task, "Thread 2");

        t1.start();
        t2.start();
    }
}

3. ReentrantLock

  • 描述ReentrantLockLock 接口的一個(gè)常用實(shí)現(xiàn),它提供了更靈活的鎖定機(jī)制闽撤,允許手動(dòng)加鎖和解鎖涧卵。
  • 示例代碼
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 示例類,演示如何使用 ReentrantLock 進(jìn)行線程同步
 */
public class LockExample {
    /** 共享計(jì)數(shù)器 */
    private int counter = 0;
    /** ReentrantLock 實(shí)例 */
    private final Lock lock = new ReentrantLock();

    /**
     * 同步遞增計(jì)數(shù)器的方法腹尖,手動(dòng)加鎖和解鎖
     */
    public void increment() {
        // 獲取鎖
        lock.lock();
        try {
            // 遞增計(jì)數(shù)器
            counter++;
            // 輸出當(dāng)前線程和計(jì)數(shù)器的值
            System.out.println(Thread.currentThread().getName() + " - Counter: " + counter);
        } finally {
            // 確保鎖在最后被釋放
            lock.unlock();
        }
    }

    /**
     * 主程序入口柳恐,創(chuàng)建多個(gè)線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)
     */
    public static void main(String[] args) {
        LockExample example = new LockExample();

        Runnable task = example::increment;

        Thread t1 = new Thread(task, "Thread 1");
        Thread t2 = new Thread(task, "Thread 2");

        t1.start();
        t2.start();
    }
}

4. ReentrantReadWriteLock

  • 描述ReentrantReadWriteLock 提供了讀寫鎖機(jī)制,可以讓多個(gè)線程并發(fā)讀取热幔,但在寫入時(shí)只有一個(gè)線程可以操作乐设。這樣可以提高在讀多寫少場(chǎng)景下的性能。
  • 示例代碼
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 示例類绎巨,演示如何使用 ReentrantReadWriteLock 進(jìn)行線程同步
 */
public class ReadWriteLockExample {
    /** 共享計(jì)數(shù)器 */
    private int counter = 0;
    /** ReentrantReadWriteLock 實(shí)例 */
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 獲取寫鎖并遞增計(jì)數(shù)器
     */
    public void increment() {
        // 獲取寫鎖
        lock.writeLock().lock();
        try {
            // 遞增計(jì)數(shù)器
            counter++;
            System.out.println(Thread.currentThread().getName() + " - Write Counter: " + counter);
        } finally {
            // 釋放寫鎖
            lock.writeLock().unlock();
        }
    }

    /**
     * 獲取讀鎖并讀取計(jì)數(shù)器
     * @return 計(jì)數(shù)器值
     */
    public int getCounter() {
        // 獲取讀鎖
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " - Read Counter: " + counter);
            return counter;
        } finally {
            // 釋放讀鎖
            lock.readLock().unlock();
        }
    }

    /**
     * 主程序入口近尚,創(chuàng)建多個(gè)線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)
     */
    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        Runnable writeTask = example::increment;
        Runnable readTask = example::getCounter;

        Thread t1 = new Thread(writeTask, "Writer Thread");
        Thread t2 = new Thread(readTask, "Reader Thread");

        t1.start();
        t2.start();
    }
}

5. Atomic

  • 描述Atomic 類位于 java.util.concurrent.atomic 包內(nèi),提供了常見(jiàn)的原子操作類(如 AtomicInteger)场勤,用于在無(wú)鎖的情況下對(duì)單一變量進(jìn)行線程安全的操作戈锻。
  • 示例代碼
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 示例類,演示如何使用 AtomicInteger 進(jìn)行線程同步
 */
public class AtomicExample {
    /** 線程安全的 AtomicInteger */
    private AtomicInteger counter = new AtomicInteger(0);

    /**
     * 原子性遞增計(jì)數(shù)器的方法
     */
    public void increment() {
        // 原子遞增
        int newValue = counter.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + " - Counter: " + newValue);
    }

    /**
     * 主程序入口和媳,創(chuàng)建多個(gè)線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)
     */
    public static void main(String[] args) {
        AtomicExample example = new AtomicExample();

        Runnable task = example::increment;

        Thread t1 = new Thread(task, "Thread 1");
        Thread t2 = new Thread(task, "Thread 2");

        t1.start();
        t2.start();
    }
}

6. CyclicBarrier

  • 描述CyclicBarrier 是一種允許一組線程相互等待的同步機(jī)制格遭,直到所有線程都到達(dá)某個(gè)共同的屏障點(diǎn)時(shí),才能繼續(xù)執(zhí)行留瞳。它支持重用拒迅,即可以被多次使用。
  • 示例代碼
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 示例類她倘,演示如何使用 CyclicBarrier 實(shí)現(xiàn)線程同步
 */
public class CyclicBarrierExample {
    /** CyclicBarrier 實(shí)例璧微,等待 3 個(gè)線程 */
    private final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        // 所有線程到達(dá)屏障后執(zhí)行的操作
        System.out.println("All threads have reached the barrier. Barrier action executed.");
    });

    /**
     * 線程任務(wù),等待屏障點(diǎn)并繼續(xù)執(zhí)行
     */
    public void performTask() {
        System.out.println(Thread.currentThread().getName() + " is waiting at the barrier");
        try {
            // 等待其他線程到達(dá)屏障點(diǎn)
            barrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " has crossed the barrier");
    }

    /**
     * 主程序入口硬梁,創(chuàng)建多個(gè)線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)


     */
    public static void main(String[] args) {
        CyclicBarrierExample example = new CyclicBarrierExample();

        Thread t1 = new Thread(example::performTask, "Thread 1");
        Thread t2 = new Thread(example::performTask, "Thread 2");
        Thread t3 = new Thread(example::performTask, "Thread 3");

        t1.start();
        t2.start();
        t3.start();
    }
}

7. Objectwait()notify() 方法

  • 描述Object 類的 wait()前硫、notify()notifyAll() 方法允許線程在某個(gè)條件下進(jìn)行等待和喚醒。與 synchronized 搭配使用荧止,可以實(shí)現(xiàn)類似于信號(hào)量的功能屹电。
  • 示例代碼
/**
 * 示例類,演示如何使用 wait() 和 notify() 進(jìn)行線程同步
 */
public class WaitNotifyExample {

    /** 自定義鎖對(duì)象 */
    private final Object lock = new Object();
    /** 標(biāo)志位罩息,表示是否已經(jīng)生產(chǎn)了數(shù)據(jù) */
    private boolean isProduced = false;

    /**
     * 生產(chǎn)者方法嗤详,等待消費(fèi)者消費(fèi)后生產(chǎn)新數(shù)據(jù)
     * @throws InterruptedException 當(dāng)線程被中斷時(shí)拋出異常
     */
    public void produce() throws InterruptedException {
        synchronized (lock) {
            // 如果已經(jīng)生產(chǎn)了數(shù)據(jù)个扰,等待消費(fèi)者消費(fèi)
            while (isProduced) {
                lock.wait();
            }
            // 生產(chǎn)數(shù)據(jù)
            System.out.println(Thread.currentThread().getName() + " produced data.");
            isProduced = true;
            // 通知消費(fèi)者可以消費(fèi)數(shù)據(jù)了
            lock.notify();
        }
    }

    /**
     * 消費(fèi)者方法瓷炮,等待生產(chǎn)者生產(chǎn)數(shù)據(jù)并進(jìn)行消費(fèi)
     * @throws InterruptedException 當(dāng)線程被中斷時(shí)拋出異常
     */
    public void consume() throws InterruptedException {
        synchronized (lock) {
            // 如果還沒(méi)有生產(chǎn)數(shù)據(jù),等待生產(chǎn)者生產(chǎn)
            while (!isProduced) {
                lock.wait();
            }
            // 消費(fèi)數(shù)據(jù)
            System.out.println(Thread.currentThread().getName() + " consumed data.");
            isProduced = false;
            // 通知生產(chǎn)者可以繼續(xù)生產(chǎn)數(shù)據(jù)了
            lock.notify();
        }
    }

    /**
     * 主程序入口递宅,創(chuàng)建生產(chǎn)者和消費(fèi)者線程并運(yùn)行
     * @param args 默認(rèn)參數(shù)
     */
    public static void main(String[] args) {
        WaitNotifyExample example = new WaitNotifyExample();

        // 創(chuàng)建生產(chǎn)者線程
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    example.produce();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Producer");

        // 創(chuàng)建消費(fèi)者線程
        Thread consumer = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    example.consume();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Consumer");

        producer.start();
        consumer.start();
    }
}

8. 總結(jié)

Java 提供了多種用于線程同步的機(jī)制娘香,包括 synchronized苍狰、ReentrantLockReentrantReadWriteLock烘绽、Atomic 類淋昭、CyclicBarrier 以及 Objectwait()/notify()。每種方式都有其適用場(chǎng)景和優(yōu)缺點(diǎn)安接。對(duì)于簡(jiǎn)單的同步需求翔忽,synchronized 是一種直接而有效的選擇;對(duì)于復(fù)雜的并發(fā)控制盏檐,Lock 提供了更靈活的鎖機(jī)制歇式;而 wait()notify() 可以實(shí)現(xiàn)線程之間的協(xié)調(diào)工作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胡野,一起剝皮案震驚了整個(gè)濱河市材失,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硫豆,老刑警劉巖龙巨,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異熊响,居然都是意外死亡旨别,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門汗茄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)昼榛,“玉大人,你說(shuō)我怎么就攤上這事剔难〉ㄓ欤” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵偶宫,是天一觀的道長(zhǎng)非迹。 經(jīng)常有香客問(wèn)我,道長(zhǎng)纯趋,這世上最難降的妖魔是什么憎兽? 我笑而不...
    開(kāi)封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮吵冒,結(jié)果婚禮上纯命,老公的妹妹穿的比我還像新娘。我一直安慰自己痹栖,他們只是感情好亿汞,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著揪阿,像睡著了一般疗我。 火紅的嫁衣襯著肌膚如雪咆畏。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天吴裤,我揣著相機(jī)與錄音旧找,去河邊找鬼。 笑死麦牺,一個(gè)胖子當(dāng)著我的面吹牛钮蛛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剖膳,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼愿卒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了潮秘?” 一聲冷哼從身側(cè)響起琼开,我...
    開(kāi)封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枕荞,沒(méi)想到半個(gè)月后柜候,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躏精,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年渣刷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矗烛。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辅柴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞭吃,到底是詐尸還是另有隱情碌嘀,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布歪架,位于F島的核電站股冗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏和蚪。R本人自食惡果不足惜止状,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望攒霹。 院中可真熱鬧怯疤,春花似錦、人聲如沸催束。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至少梁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矫付,已是汗流浹背凯沪。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留买优,地道東北人妨马。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像杀赢,于是被迫代替她去往敵國(guó)和親烘跺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容