日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

互斥同步(synchronized、Lock、ReentrantLock、ReadWriteLock、ReentrantReadWriteLock)

發(fā)布時間:2024/10/14 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 互斥同步(synchronized、Lock、ReentrantLock、ReadWriteLock、ReentrantReadWriteLock) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

互斥同步

Java 提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問:

  • ?JVM 實現(xiàn)的 synchronized
  • JDK 實現(xiàn)的 ReentrantLock
  • 1. synchronized

    synchronized關(guān)鍵字保證在同一時刻,只有一個線程可以執(zhí)行某個對象內(nèi)某一個方法或某一段代碼塊。

    重量級鎖。包含兩個特征:互斥性和可見性。

    synchronized可以解決一個線程看到對象處于不一致的狀態(tài),可以保證進入同步方法或者同步代碼塊的每個線程都可以看到由同一個鎖保護之前所有的修改效果。

    實現(xiàn)同步的基礎(chǔ):Java中每個對象都可作為鎖。

    1.1 同步一個代碼塊

    public void func() {synchronized (this) {// ...} }

    它只作用于同一個對象,如果調(diào)用兩個對象上的同步代碼塊,就不會進行同步。

    對于以下代碼,使用 ExecutorService 執(zhí)行了兩個線程,由于調(diào)用的是同一個對象的同步代碼塊,因此這兩個線程會進行同步。

    當(dāng)一個線程進入同步語句塊時,另一個線程就必須等待。

    public class SynchronizedExample {public void func1() {synchronized (this) {for (int i = 0; i < 10; i++) {System.out.print(i + " ");}}} }public static void main(String[] args) {SynchronizedExample e1 = new SynchronizedExample();ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> e1.func1()); executorService.execute(() -> e1.func1()); }

    運行結(jié)果:

    對于以下代碼,兩個線程調(diào)用了不同對象的同步代碼塊,因此這兩個線程就不需要同步。

    從輸出結(jié)果可以看出,兩個線程交叉執(zhí)行。

    public static void main(String[] args) {SynchronizedExample e1 = new SynchronizedExample();SynchronizedExample e2 = new SynchronizedExample();ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> e1.func1());executorService.execute(() -> e2.func1()); }

    運行結(jié)果:

    1.2 同步一個方法

    public synchronized void func () {// ... }

    它和同步代碼塊一樣,作用于同一個對象。

    1.3?同步一個類

    public void func() {synchronized (SynchronizedExample.class) {// ...} }

    作用于整個類,也就是說兩個線程調(diào)用同一個類的不同對象上的這種同步語句,也會進行同步。

    public class SynchronizedExample {public void func2() {synchronized (SynchronizedExample.class) {for (int i = 0; i < 10; i++) {System.out.print(i + " ");}}} }public static void main(String[] args) {SynchronizedExample e1 = new SynchronizedExample();SynchronizedExample e2 = new SynchronizedExample();ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> e1.func2());executorService.execute(() -> e2.func2()); }

    運行結(jié)果:

    1.4?同步一個靜態(tài)方法

    public synchronized static void fun() {// ... }

    作用于整個類。

    1.5 synchronized缺陷:

    • 不可中斷:A執(zhí)行,等待。當(dāng)A阻塞時,B得一直等待
    • 如果多個線程都只是進行讀操作,當(dāng)一個線程在進行讀操作時,其他線程只能等待無法進行讀操作。(一般上希望讀鎖是共享鎖,而寫鎖是排它鎖)
    • 無法知道線程有沒有成功獲取到鎖

    2. java.util.concurrent.locks包中常用的類和接口

    2.1 Lock

    Lock是一個接口:

    public interface Lock {void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition(); }

    采用Lock,必須主動去釋放鎖,并且在發(fā)生異常時,不會自動釋放鎖。

    因此一般來說,使用Lock必須在try{}catch{}塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發(fā)生。

    2.1.1 獲取鎖

    lock()方法

    是平常使用得最多的用來獲取鎖的一個方法。如果鎖已被其他線程獲取,則進行等待。

    Lock lock = ...; lock.lock(); try{... //處理任務(wù) }catch(Exception ex){... }finally{lock.unlock(); //釋放鎖 }

    ?tryLock()方法

    有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

    Lock lock = ...; if(lock.tryLock()) {try{...//處理任務(wù)}catch(Exception ex){...}finally{lock.unlock(); //釋放鎖} }else {...//如果不能獲取鎖,則直接做其他事情 }

    lockInterruptibly()方法

    比較特殊,當(dāng)通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應(yīng)中斷,即中斷線程的等待狀態(tài)。

    也就使說,當(dāng)兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那么對線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過程。

    由于lockInterruptibly()的聲明中拋出了異常,所以lock.lockInterruptibly()必須放在try塊中或者在調(diào)用lockInterruptibly()的方法外聲明拋出InterruptedException。

    public void method() throws InterruptedException {lock.lockInterruptibly();try { //.....}finally {lock.unlock();} }

    ??注意,當(dāng)一個線程獲取了鎖之后,是不會被interrupt()方法中斷的。

    因為單獨調(diào)用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。

    因此當(dāng)通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應(yīng)中斷的。

    而用synchronized修飾的話,當(dāng)一個線程處于等待某個鎖的狀態(tài),是無法被中斷的,只有一直等待下去。

    2.2 ReentrantLock

    ReentrantLock 是 java.util.concurrent(J.U.C) 包中的鎖。

    唯一實現(xiàn)了Lock接口的類,并且ReentrantLock提供了更多的方法。

    2.2.1 lock()

    public class Test {private ArrayList<Integer> arrayList = new ArrayList<Integer>();public static void main(String[] args) {final Test test = new Test(); new Thread(){public void run() {test.insert(Thread.currentThread());};}.start();new Thread(){public void run() {test.insert(Thread.currentThread());};}.start();} public void insert(Thread thread) {Lock lock = new ReentrantLock(); //注意這個地方lock.lock();try {System.out.println(thread.getName()+"得到了鎖");for(int i=0;i<5;i++) {arrayList.add(i);}} catch (Exception e) {// TODO: handle exception}finally {System.out.println(thread.getName()+"釋放了鎖");lock.unlock();}} }

    運行結(jié)果:

    Thread-0得到了鎖

    Thread-1得到了鎖

    Thread-0釋放了鎖

    Thread-1釋放了鎖

    為什么第二個線程怎么會在第一個線程釋放鎖之前得到了鎖?

    原因:在insert方法中的lock變量是局部變量,每個線程執(zhí)行該方法時都會保存一個副本,那么理所當(dāng)然每個線程執(zhí)行到lock.lock()處獲取的是不同的鎖,所以就不會發(fā)生沖突。

    知道了原因改起來就比較容易了,只需要將lock聲明為類的屬性即可。

    import java.util.ArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class Test{Lock lock = new ReentrantLock(); //注意這個地方private ArrayList<Integer> arrayList = new ArrayList<Integer>();public static void main(String[] args) {final Testtest = new Test(); new Thread(){public void run() {test.insert(Thread.currentThread());};}.start();new Thread(){public void run() {test.insert(Thread.currentThread());};}.start();} public void insert(Thread thread) {lock.lock();try {System.out.println(thread.getName()+"得到了鎖");for(int i=0;i<5;i++) {arrayList.add(i);}} catch (Exception e) {// TODO: handle exception}finally {System.out.println(thread.getName()+"釋放了鎖");lock.unlock();}} }

    2.2.2 tryLock()

    package Test1;import java.util.ArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class Code1 {Lock lock = new ReentrantLock(); //注意這個地方private ArrayList<Integer> arrayList = new ArrayList<Integer>();public static void main(String[] args) {final Code1 test = new Code1(); new Thread(){public void run() {test.insert(Thread.currentThread());};}.start();new Thread(){public void run() {test.insert(Thread.currentThread());};}.start();} public void insert(Thread thread) {if(lock.tryLock()) {try {System.out.println(thread.getName()+"得到了鎖");for(int i=0;i<5;i++) {arrayList.add(i);}} catch (Exception e) {// TODO: handle exception}finally {System.out.println(thread.getName()+"釋放了鎖");lock.unlock();}}else {System.out.println(thread.getName()+"未獲取到鎖");}} }

    運行結(jié)果:

    Thread-0得到了鎖
    Thread-0釋放了鎖
    Thread-1未獲取到鎖

    2.2.3?lockInterruptibly()

    package Test1;import java.util.concurrent.locks.*;public class Code1 implements Runnable{Lock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Code1 test = new Code1();//線程1Thread thread1 = new Thread(test);thread1.start();//線程2Thread thread2 = new Thread(test);thread2.start();//讓主線程等待兩秒,讓線程2嘗試獲取線程1中的鎖,兩秒后嘗試中斷線程2Thread.sleep(2000);thread2.interrupt();}public void run(){try {insert();} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName()+"被中斷");e.printStackTrace();}} public void insert() throws InterruptedException {lock.lockInterruptibly();/* 要想得到正確的中斷等待線程,* 獲取鎖必須放在trycatch語句塊外* 然后拋出InterruptedException*/System.out.println(Thread.currentThread().getName() + "獲取了鎖");try {for (;;) {}} finally {lock.unlock();System.out.println(Thread.currentThread().getName() + "釋放了鎖");}} }

    ?

    運行結(jié)果:

    Thread-0獲取了鎖
    Thread-1被中斷
    java.lang.InterruptedException...

    public class LockExample {private Lock lock = new ReentrantLock();public void func() {lock.lock();try {for (int i = 0; i < 10; i++) {System.out.print(i + " ");}} finally {lock.unlock(); // 確保釋放鎖,從而避免發(fā)生死鎖。}} }public static void main(String[] args) {LockExample lockExample = new LockExample();ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(() -> lockExample.func());executorService.execute(() -> lockExample.func()); }

    運行結(jié)果:

    2.3 ReadWriteLock

    ReadWriteLock也是一個接口,在它里面只定義了兩個方法:

    public interface ReadWriteLock {/*** Returns the lock used for reading.** @return the lock used for reading.*/Lock readLock();/*** Returns the lock used for writing.** @return the lock used for writing.*/Lock writeLock(); }

    一個用來獲取讀鎖,一個用來獲取寫鎖。

    也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。

    下面的ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口。

    2.4?ReentrantReadWriteLock

    里面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。

    2.4.1?假如有多個線程要同時進行讀操作的話,先看一下synchronized達(dá)到的效果:

    package Test1;import java.util.concurrent.locks.ReentrantReadWriteLock;public class Code1 {private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();public static void main(String[] args) {final Code1 test = new Code1();new Thread() {public void run() {test.get(Thread.currentThread());};}.start();new Thread() {public void run() {test.get(Thread.currentThread());};}.start();}public synchronized void get(Thread thread) {long start = System.currentTimeMillis();while (System.currentTimeMillis() - start <= 1) {System.out.println(thread.getName() + "正在進行讀操作");}System.out.println(thread.getName() + "讀操作完畢");} }

    運行結(jié)果:

    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0讀操作完畢
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1讀操作完畢

    直到thread1執(zhí)行完讀操作之后,才會打印thread2執(zhí)行讀操作的信息。

    2.4.2?改成用讀寫鎖:

    package Test1;import java.util.concurrent.locks.ReentrantReadWriteLock;public class Code1 {private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();public static void main(String[] args) {final Code1 test = new Code1();new Thread() {public void run() {test.get(Thread.currentThread());};}.start();new Thread() {public void run() {test.get(Thread.currentThread());};}.start();}//更改此處public void get(Thread thread) {rwl.readLock().lock();try {long start = System.currentTimeMillis();while (System.currentTimeMillis() - start <= 1) {System.out.println(thread.getName() + "正在進行讀操作");}System.out.println(thread.getName() + "讀操作完畢");} finally {rwl.readLock().unlock();}} }

    運行結(jié)果:

    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-0正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1正在進行讀操作
    Thread-1讀操作完畢
    Thread-0讀操作完畢

    thread1和thread2在同時進行讀操作,可大大提升了讀操作的效率。

    如果有一個線程已經(jīng)占用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。

    如果有一個線程已經(jīng)占用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。

    3. 比較

    ?synchronizedReentrantLock鎖的實現(xiàn)鎖的釋放?性能等待可中斷*公平鎖*鎖綁定多個條件適用情況
    JVM 實現(xiàn)的JDK 實現(xiàn)的
    自動釋放手動finally塊中,unlock()
    無法判斷是否獲取鎖的狀態(tài),?

    新版本 Java 對 synchronized 進行了很多優(yōu)化

    例如自旋鎖等;

    與ReentrantLock 大致相同。

    ?
    不行可中斷
    非公平的也是非公平的,但是
    也可以是公平的。
    ?可以同時綁定多個 Condition 對象
    適合代碼少量的同步問題大量同步的代碼的同步問題

    等待可中斷:當(dāng)持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情。
    公平鎖:公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。

    參考:https://blog.csdn.net/qq_19734597/article/details/80874972

    總結(jié)

    以上是生活随笔為你收集整理的互斥同步(synchronized、Lock、ReentrantLock、ReadWriteLock、ReentrantReadWriteLock)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。