溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java中的線程同步是什么

發(fā)布時(shí)間:2020-06-18 10:55:35 來源:億速云 閱讀:193 作者:Leah 欄目:編程語言

java中的線程同步是什么?相信大部分人都不太了解,今天小編為了讓大家更加了解線程同步,給大家總結(jié)了以下內(nèi)容,跟隨小編一起來看看吧。

線程同步

多線程之間調(diào)用同一對(duì)象時(shí),為了運(yùn)行的安全和準(zhǔn)確性,需要對(duì)該對(duì)象進(jìn)行同步,確保每一個(gè)線程用到的時(shí)候該對(duì)象的結(jié)果都是正確的,該對(duì)象的狀態(tài)都是合理的,這部分涉及到同步、線程鎖等知識(shí)點(diǎn)。這部分的只是就涉及到了synchronized、同步鎖(Lock)的概念。

synchronized

synchronized關(guān)鍵詞可以修飾對(duì)象、方法,通常用法如下:

//同步代碼塊
synchronized(Object object){
...
}
//或者
//同步方法
public synchronized void test(){
...
}

其中有一個(gè)同步監(jiān)視器的概念,比如上面同步代碼塊的object對(duì)象以及同步方法的this對(duì)象就會(huì)同步監(jiān)視,多個(gè)線程同時(shí)調(diào)用一個(gè)同步的代碼塊或者方法時(shí),在任何時(shí)刻只能夠一個(gè)線程能夠獲得該同步監(jiān)視的對(duì)象鎖,執(zhí)行完代碼之后才會(huì)釋放該鎖,在此期間其他調(diào)用的線程只能等待該鎖被釋放后才能調(diào)用。

上文中提到的SellRunnable類中的sell方法也用到了synchronized,上文中代碼執(zhí)行太快,所以感知不到,如果修改一下就能明白有沒有synchronized的區(qū)別了。

public class ThreadTest {
    public static void main(String[] args) {
        SellRunnable sellRunnable = new SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1");
        Thread thread2 = new Thread(sellRunnable, "2");
        Thread thread3 = new Thread(sellRunnable, "3");
        thread2.start();
        thread1.start();
        thread3.start();
    }
}
class SellRunnable implements Runnable {
    //有十張票
    int index = 10;
    public void sell() {
        if (index >= 1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            index--;
            System.out.println("售貨窗口:" + Thread.currentThread().getName() + 
            " 賣出了一張票,剩余:
            " + index);
        } else {
            System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 買票時(shí)沒票了");
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 開始買票");
            sell();
        }
    }
}
//執(zhí)行結(jié)果:
售貨窗口:1 開始買票
售貨窗口:2 開始買票
售貨窗口:3 開始買票
售貨窗口:2  賣出了一張票,剩余:9
售貨窗口:2 開始買票
售貨窗口:1  賣出了一張票,剩余:9
售貨窗口:1 開始買票
售貨窗口:3  賣出了一張票,剩余:8
售貨窗口:3 開始買票
售貨窗口:1  賣出了一張票,剩余:6
售貨窗口:1 開始買票
售貨窗口:2  賣出了一張票,剩余:6
售貨窗口:2 開始買票
售貨窗口:3  賣出了一張票,剩余:5
售貨窗口:3 開始買票
售貨窗口:1  賣出了一張票,剩余:4
售貨窗口:1 開始買票
售貨窗口:2  賣出了一張票,剩余:3
售貨窗口:3  賣出了一張票,剩余:2
售貨窗口:3 開始買票
售貨窗口:2 開始買票
售貨窗口:3  賣出了一張票,剩余:1
售貨窗口:2  賣出了一張票,剩余:0
售貨窗口:1  賣出了一張票,剩余:1
Process finished with exit code 0  //可以看到,票數(shù)減少是錯(cuò)誤的
//sell方法添加synchronized修飾符后 執(zhí)行結(jié)果:
public synchronized void sell() {
        if (index >= 1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            index--;
            System.out.println("售貨窗口:" + Thread.currentThread().getName() + 
            " 賣出了一張票,剩余:
            " + index);
        } else {
            System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 買票時(shí)沒票了");
        }
    }
售貨窗口:2 開始買票
售貨窗口:3 開始買票
售貨窗口:1 開始買票
售貨窗口:2  賣出了一張票,剩余:9
售貨窗口:2 開始買票
售貨窗口:1  賣出了一張票,剩余:8
售貨窗口:1 開始買票
售貨窗口:3  賣出了一張票,剩余:7
售貨窗口:3 開始買票
售貨窗口:1  賣出了一張票,剩余:6
售貨窗口:1 開始買票
售貨窗口:2  賣出了一張票,剩余:5
售貨窗口:2 開始買票
售貨窗口:1  賣出了一張票,剩余:4
售貨窗口:1 開始買票
售貨窗口:1  賣出了一張票,剩余:3
售貨窗口:1 開始買票
售貨窗口:3  賣出了一張票,剩余:2
售貨窗口:3 開始買票
售貨窗口:1  賣出了一張票,剩余:1
售貨窗口:1 開始買票
售貨窗口:1  賣出了一張票,剩余:0
售貨窗口:2 買票時(shí)沒票了
售貨窗口:3 買票時(shí)沒票了
Process finished with exit code 0  // 可以看到,票數(shù)是正常減少的

以上對(duì)于sell方法進(jìn)行同步之后,在某一瞬間,只會(huì)有一個(gè)線程調(diào)用該方法,所以里面判斷index的時(shí)候得到的結(jié)果就是正確的結(jié)果。

以上同步的時(shí)候,是以降低運(yùn)行效率的方式來保證線程安全的,為此,不要對(duì)線程使用類中沒必要的方法、對(duì)象進(jìn)行同步標(biāo)識(shí),只對(duì)有競爭的資源或者代碼進(jìn)行同步標(biāo)識(shí)。

同步標(biāo)識(shí)后,有以下幾點(diǎn)可以釋放該鎖:

代碼塊、方法執(zhí)行完畢(正常完畢、return或break、拋出異常)

調(diào)用了wait方法,使得當(dāng)前線程暫停。

當(dāng)線程執(zhí)行到同步代碼塊時(shí),sleep、yield方法不會(huì)釋放該同步鎖,掛起方法suspend也不會(huì)(線程操作過程中盡量避免使用suspend、resume來操作線程狀態(tài),容易導(dǎo)致死鎖。)

同步鎖Lock

上文中提到的synchronized是java中的一個(gè)關(guān)鍵詞,也提到了在sleep的時(shí)候、進(jìn)行IO操作的時(shí)候該線程不會(huì)釋放線程鎖,其他線程就需要一直等待,這樣有時(shí)會(huì)降低執(zhí)行的效率,所以就需要一個(gè)可以在線程阻塞時(shí)可以釋放線程鎖的替代方案,Lock就是為了解決這個(gè)問題出現(xiàn)的。

Lock是一個(gè)java中的類,在java.util.concurrent.locks包中,具體的代碼如下:

public interface Lock {
    void lock();//加鎖
    void lockInterruptibly() throws InterruptedException;//加鎖
    boolean tryLock();//加鎖
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//加鎖
    void unlock();//釋放鎖
    Condition newCondition();//線程協(xié)作中用到
}

Lock接口的一個(gè)實(shí)現(xiàn)子類為ReentrantLock,在java.util.concurrent.locks包下,ReentrantLock的源代碼如下:

public class ReentrantLock implements Lock, Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final ReentrantLock.Sync sync;
    public ReentrantLock() {
        this.sync = new ReentrantLock.NonfairSync();
    }
    public ReentrantLock(boolean var1) {//是否創(chuàng)建公平鎖
        this.sync = (ReentrantLock.Sync)(var1?new ReentrantLock.FairSync():new  ReentrantLock.
        NonfairSync());
    }
    public void lock() {
        this.sync.lock();
    }
    public void lockInterruptibly() throws InterruptedException {
        this.sync.acquireInterruptibly(1);
    }
    public boolean tryLock() {
        return this.sync.nonfairTryAcquire(1);
    }
    public boolean tryLock(long var1, TimeUnit var3) throws InterruptedException {
        return this.sync.tryAcquireNanos(1, var3.toNanos(var1));
    }
    public void unlock() {
        this.sync.release(1);
    }
    public Condition newCondition() {
        return this.sync.newCondition();
    }
    public int getHoldCount() {//當(dāng)前線程持有該鎖的數(shù)量
        return this.sync.getHoldCount();
    }
    public boolean isHeldByCurrentThread() {//該鎖是否被當(dāng)前線程持有
        return this.sync.isHeldExclusively();
    }
    public boolean isLocked() {//是否被其他線程持有該鎖
        return this.sync.isLocked();
    }
    public final boolean isFair() {//是否是公平鎖
        return this.sync instanceof ReentrantLock.FairSync;
    }
    protected Thread getOwner() {//當(dāng)前鎖的持有線程
        return this.sync.getOwner();
    }
    public final boolean hasQueuedThreads() {//是否有線程在等待該鎖
        return this.sync.hasQueuedThreads();
    }
    public final boolean hasQueuedThread(Thread var1) {//目標(biāo)線程是否在等待該鎖
        return this.sync.isQueued(var1);
    }
    public final int getQueueLength() {//等待該鎖線程的數(shù)量
        return this.sync.getQueueLength();
    }
    protected Collection<Thread> getQueuedThreads() {//獲取所有等待該鎖的線程集合
        return this.sync.getQueuedThreads();
    }
    ...
    
}

Lock的使用方法

lock

lock() 用來獲取鎖,如果該鎖被其他線程占用,則進(jìn)入等待。

public class LockTest {
    public static void main(String[] args) {
        com.test.java.SellRunnable sellRunnable = new com.test.java.SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1號(hào)窗口");
        Thread thread2 = new Thread(sellRunnable, "2號(hào)窗口");
        Thread thread3 = new Thread(sellRunnable, "3號(hào)窗口");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
public class SellRunnable implements Runnable {
    //有十張票
    int index = 10;
    Lock lock = new ReentrantLock();
    public void sell() {
        try {
            lock.lock();
            System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
            "獲取了票源+++++");
            if (index >= 1) {
                index--;
                System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                "賣出了一張票,剩余:
                " + index);
            } else {
                System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                "買票時(shí)沒票了000");
            }
        } finally {
            lock.unlock();
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
}

運(yùn)行結(jié)果:

售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口買票時(shí)沒票了000
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口買票時(shí)沒票了000
Process finished with exit code 0  //每一個(gè)窗口都隨機(jī)獲取票源、然后賣出票

tryLock

tryLock()嘗試獲取鎖,如果獲取成功返回true,如果失敗,則返回false,不會(huì)進(jìn)入等待狀態(tài)。

public class SellRunnable implements Runnable {
    //有十張票
    int index = 10;
    Lock lock = new ReentrantLock();
    public void sell() {
        if (lock.tryLock()) {
            try {
                System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                "獲取了票源+++++");
                if (index >= 1) {
                    index--;
                    System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                    "賣出了一張票,剩余:" + index);
                } else {
                    System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                    "買票時(shí)沒票了000");
                }
            } finally {
                lock.unlock();
            }
        } else {
           System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName()+"沒有獲取票源?。?!");
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
}

運(yùn)行結(jié)果:

售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口沒有獲取票源?。。?
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。。?
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。。?
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。?
Process finished with exit code 0//沒有獲取到貨源的票口,就直接沒有等待,進(jìn)入下次買票

tryLock(long time, TimeUnit unit)

tryLock(long time, TimeUnit unit)可以設(shè)置拿不到鎖的時(shí)候等待一段時(shí)間。//第一個(gè)參數(shù)時(shí)常長,第二個(gè)參數(shù)時(shí)間單位

public class SellRunnable implements Runnable {
    //有十張票
    int index = 10;
    Lock lock = new ReentrantLock();
    public void sell() {
        try {
            if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
                try {
                    System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                    "獲取了票源+++++");
                    if (index >= 1) {
                        index--;
                        System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName()
                         +"賣出了一張票,剩余:" + index);
                    } else {
                        System.out.println("售貨柜臺(tái):" + Thread.currentThread().
                        getName()  + "買票時(shí)沒票了000");
                    }
                    try {
                        Thread.sleep(2000);//人為加入買票時(shí)間
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + 
                "沒有獲取票源?。?!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        while (index > 0) {
            try {
                Thread.sleep(500);//要不執(zhí)行太快,看不出效果
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
}

執(zhí)行結(jié)果:

售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!??!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源!??!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!!!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源!??!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):1號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。。?
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!??!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源!??!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口沒有獲取票源!?。?
售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。。?
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
Process finished with exit code 0 //當(dāng)買票時(shí)間大約等待時(shí)間時(shí),則沒有獲取票源的窗口不買票,進(jìn)入下個(gè)買票機(jī)會(huì)

將買票時(shí)間縮短:

try {
    Thread.sleep(500);//人為加入買票時(shí)間
} catch (InterruptedException e) {
    e.printStackTrace();
}

執(zhí)行結(jié)果:

售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!??!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口買票時(shí)沒票了000
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口買票時(shí)沒票了000
Process finished with exit code 0 //等待時(shí)間內(nèi)獲取到票源了,也就賣出票了

lockInterruptibly

lockInterruptibly()通過該方法獲取鎖時(shí),如果該鎖正在被其他線程持有,則進(jìn)入等待狀態(tài),但是這個(gè)等待過程是可以被中斷的,通過調(diào)用Thread對(duì)象的interrupt方法就可中斷等待,中斷時(shí)拋出異常InterruptedException,需要捕獲或者聲明拋出。

public class ThreadTest {
    public static void main(String[] args) {
        SellRunnable sellRunnable = new SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1號(hào)窗口");
        Thread thread2 = new Thread(sellRunnable, "2號(hào)窗口");
        Thread thread3 = new Thread(sellRunnable, "3號(hào)窗口");
        thread1.start();
        try {
            Thread.sleep(500);//確保窗口1號(hào)先獲取鎖
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
        thread3.start();
        try {
            Thread.sleep(2000);//等待兩秒后,打斷窗口2、3的等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
        thread3.interrupt();
    }
}
SellRunnable中等待時(shí)間加長:
try {
    Thread.sleep(5000);//人為加入買票時(shí)間
} catch (InterruptedException e) {
    e.printStackTrace();
}

執(zhí)行結(jié)果:

售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):3號(hào)窗口被打斷了      //這個(gè)地方被打斷了
售貨柜臺(tái):2號(hào)窗口被打斷了      //這個(gè)地方被打斷了
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口買票時(shí)沒票了000
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口買票時(shí)沒票了000
Process finished with exit code 0

synchronized和Lock對(duì)比

通過以上代碼,可以看出Lock和synchronized的幾點(diǎn)關(guān)聯(lián)和區(qū)別:

兩者都是可重入鎖

可重入鎖是指當(dāng)一個(gè)線程獲得對(duì)象鎖之后,該線程可以再次獲取該對(duì)象的鎖而不被阻塞。比如同一個(gè)類中有多個(gè)方法(或一個(gè)方法遞歸調(diào)用)被synchronized修飾或者被Lock加持后,同一個(gè)線程在調(diào)用這兩個(gè)方法時(shí)都可以獲取該對(duì)象的鎖而不被阻塞。

不可重入鎖的示例:

public class Lock{
    private boolean isLocked = false;
    public void lock(){
        while(isLocked){    
            wait();
        }
        isLocked = true;
    }
    public void unlock(){
        isLocked = false;
        notify();
    }
}
//使用方法:
public class Test{
    Lock lock = new Lock();
    public void test1(){
        lock.lock();
        test2();
        lock.unlock();
    }
    public void test2(){
        lock.lock();
        ...
        lock.unlock();
    }
}

Test類在調(diào)用test1方法的時(shí)候,執(zhí)行完lock.lock()后調(diào)用test2的時(shí)候,就會(huì)一直等待,變成死鎖。

可重入鎖設(shè)計(jì)原理:

public class Lock{
    private boolean isLocked = false;
    private Thread lockedThread = null;
    int lockedCount = 0;
    public void lock(){
        Thread thread = Thread.currentThread();
        while(isLocked && thread != lockedThread){    
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedThread = thread;
    }
    public void unlock(){
        Thread thread = Thread.currentThread();
        if(thread == lockedThread){    
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                lockedThread = null;
                notify();
            }
        }
    }
}

這樣調(diào)用Test類的test1方法后,test2方法也能順利被執(zhí)行。

synchronized在實(shí)現(xiàn)上也基本上是采用記數(shù)器的方式來實(shí)現(xiàn)可重入的。

Lock是可中斷鎖,synchronized不可中斷。

當(dāng)一個(gè)線程B執(zhí)行被鎖的對(duì)象的代碼時(shí),發(fā)現(xiàn)線程A已經(jīng)持有該鎖,那么線程B就會(huì)進(jìn)入等待,但是synchronized就無法中斷該等待過程,而Lock就可以通過lockInterruptibly方法拋出異常從而中斷等待,去處理別的事情。

Lock可創(chuàng)建公平鎖,synchronized是非公平鎖。

公平鎖的意思是按照請(qǐng)求的順序來獲取鎖,不平公鎖就無法保證線程獲取鎖的先后次序。

Lock可以知道是否獲取到鎖,synchronized不可以。

synchronized在發(fā)生異常或者運(yùn)行完畢,會(huì)自動(dòng)釋放線程占有的鎖。而Lock需要主動(dòng)釋放鎖,否則會(huì)鎖死;

synchronized在阻塞時(shí),別的線程無法獲取鎖,Lock可以(這也是lock設(shè)計(jì)的一個(gè)目的)。

讀寫鎖

多個(gè)線程對(duì)同一個(gè)文件進(jìn)行寫操作時(shí),會(huì)發(fā)生沖突所以需要加鎖,但是對(duì)同一個(gè)文件進(jìn)行讀操作的時(shí)候,使用上面的方法會(huì)造成效率的降低,所以基于這種情況,產(chǎn)生了ReadWriteLock這個(gè)接口:

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();//寫的鎖
}

這個(gè)接口的實(shí)現(xiàn)類是ReentrantReadWriteLock,其源代碼如下:

public class ReentrantReadWriteLock implements ReadWriteLock, Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    ...
    public ReentrantReadWriteLock.WriteLock writeLock() {//獲取write lock
        return this.writerLock;
    }
    public ReentrantReadWriteLock.ReadLock readLock() {//獲取read lock
        return this.readerLock;
    }
    ...
}

使用方法和Lock一樣,使用到write時(shí)調(diào)用writeLock()方法獲取lock進(jìn)行加鎖,使用到read時(shí)調(diào)用readLock()方法進(jìn)行加鎖,需要注意的知識(shí)點(diǎn)如下:

線程A占用寫鎖,線程B在申請(qǐng)寫、讀的時(shí)候需要等待。

線程A占用讀鎖,線程B在申請(qǐng)寫操作時(shí),需要等待。

線程A占用讀鎖,線程B獲取讀操作時(shí)可以獲取到。

總結(jié)

如果需要效率提升,則建議使用Lock,如果效率要求不高,則synchronized滿足使用條件,業(yè)務(wù)邏輯寫起來也簡單,不需要手動(dòng)釋放鎖。

億速云,有大量免費(fèi)的JAVA入門教程,歡迎大家學(xué)習(xí)!

看完上述內(nèi)容,你們對(duì)java中的線程同步有進(jìn)一步的了解嗎?如果還想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI