Java線程同步

前面介紹了進(jìn)程與線程的關(guān)系及其啟動(dòng)和銷毀方式以及線程中的知識(shí)點(diǎn)漾唉,使用多線程的時(shí)候會(huì)涉及到數(shù)據(jù)同步問題赵刑,本文梳理一下線程同步相關(guān)知識(shí)點(diǎn)般此。

線程同步

多線程之間調(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ìng)爭(zhēng)的資源或者代碼進(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() + "沒有獲取票源R教薄!旧困!");
        }


    }

    @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)窗口沒有獲取票源4挤荨!吼具!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源A欧住!拗盒!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口沒有獲取票源2澜摺!陡蝇!
售貨柜臺(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)窗口沒有獲取票源H簟!登夫!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源9愠住!恼策!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口沒有獲取票源Q恢隆!!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源7滞佟?古觥!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口沒有獲取票源U狼恰;∮!
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口沒有獲取票源F础:慈馈!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口沒有獲取票源0鞍>榫酢!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源6锰印5辽取!
售貨柜臺(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)窗口沒有獲取票源3撂睢A屏ァ!
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):3號(hào)窗口沒有獲取票源R砟帧0弑恰!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源A攒<崛酢!

Process finished with exit code 0//沒有獲取到貨源的票口关摇,就直接沒有等待荒叶,進(jìn)入下次買票
  • tryLock(long time, TimeUnit unit)

tryLock(long time, TimeUnit unit)可以設(shè)置拿不到鎖的時(shí)候等待一段時(shí)間。//第一個(gè)參數(shù)時(shí)常長(zhǎng)输虱,第二個(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() + "沒有獲取票源P╅埂!宪睹!");
            }
        } 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)窗口沒有獲取票源!C道宅!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口沒有獲取票源J程!污茵!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源S1ā!泞当!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口沒有獲取票源<8颉!襟士!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源5领!陋桂!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):2號(hào)窗口沒有獲取票源D嫒ぁ!嗜历!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源P!梨州!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口沒有獲取票源:鄞选!暴匠!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源0盎帧!每窖!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):1號(hào)窗口沒有獲取票源S行颉!岛请!
售貨柜臺(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)窗口沒有獲取票源M蠢琛!刮吧!
售貨柜臺(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)窗口沒有獲取票源D拱荨!惶桐!

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)窗口沒有獲取票源R;哐堋!
售貨柜臺(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)窗口沒有獲取票源>群蕖C潮病!
售貨柜臺(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í)間加長(zhǎng):
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ù)邏輯寫起來也簡(jiǎn)單拍霜,不需要手動(dòng)釋放鎖。

進(jìn)程與線程的創(chuàng)建和銷毀
線程知識(shí)點(diǎn)總結(jié)
線程同步

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末薪介,一起剝皮案震驚了整個(gè)濱河市祠饺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汁政,老刑警劉巖道偷,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異记劈,居然都是意外死亡勺鸦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門抠蚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祝旷,“玉大人,你說我怎么就攤上這事嘶窄』初耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵柄冲,是天一觀的道長(zhǎng)吻谋。 經(jīng)常有香客問我,道長(zhǎng)现横,這世上最難降的妖魔是什么漓拾? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮戒祠,結(jié)果婚禮上骇两,老公的妹妹穿的比我還像新娘。我一直安慰自己姜盈,他們只是感情好低千,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馏颂,像睡著了一般示血。 火紅的嫁衣襯著肌膚如雪棋傍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天难审,我揣著相機(jī)與錄音瘫拣,去河邊找鬼。 笑死告喊,一個(gè)胖子當(dāng)著我的面吹牛麸拄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播葱绒,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼感帅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了地淀?” 一聲冷哼從身側(cè)響起失球,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎帮毁,沒想到半個(gè)月后实苞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烈疚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年黔牵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爷肝。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猾浦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灯抛,到底是詐尸還是另有隱情金赦,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布对嚼,位于F島的核電站夹抗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纵竖。R本人自食惡果不足惜漠烧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望靡砌。 院中可真熱鬧已脓,春花似錦、人聲如沸通殃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至恨诱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骗炉,已是汗流浹背照宝。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留句葵,地道東北人厕鹃。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乍丈,于是被迫代替她去往敵國(guó)和親剂碴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355