第七章 線程的活性故障--《java多線程編程實戰指南-核心篇》

線程活性故障是由資源稀缺型或者程序自身的問題和缺陷導致線程一直處於非RUNNABLE狀態,或者線程雖然處於RUNNABLE狀態但是其要執行的任務卻一直無法進展的故障現象。

死鎖

如果兩個或者更多的線程因相互等待對方而被永遠暫停,那麼我們就稱這些線程產生了死鎖。

哲學家就餐問題

一個線程在持有一個鎖L1的情況下去申請另外一個鎖L2,而另外一個線程也在持有一個鎖L2的情況下去申請另外一個鎖L1,那麼就產生了死鎖。

哲學家思考問題案例V1:

package JavaCoreThreadPatten.capter07.v1;

/**
 * 模擬多線程:哲學家喫飯問題
 * 筷子類
 */
public class Chopstick {
    public final int id;
    public Status status = Status.DOWN;

    public Chopstick(int id){
        this.id = id;
    }

    /**
     * 拿起筷子
     */
    public void pickUp(){
        this.status = Status.UP;
    }

    /**
     * 放下筷子
     */
    public void putDown(){
        this.status = Status.DOWN;
    }

    public int getId() {
        return id;
    }

    public enum Status{
        UP,DOWN;
    }
}
package JavaCoreThreadPatten.capter07.v1;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 哲學家:一會思考,一會喫飯,先拿起左手筷子再拿起右手筷子,最終再喫飯,
 */
public class Philosophers extends Thread {
    private final int id;
    private final Chopstick left;
    private final Chopstick right;

    public Philosophers(int id, Chopstick left, Chopstick right) {
        this.id = id;
        this.left = left;
        this.right = right;
    }


    public void eat() {
        synchronized (left) {
            left.pickUp();
            System.out.println("哲學家" + id + "拿起了左手邊的筷子" + left.getId() + ",開始準備拿起右手邊的筷子。。。" + right.getId());
            synchronized (right) {
                right.pickUp();
                System.out.println("哲學家" + id + "拿起了右手邊的筷子" + right.getId() + ",開始喫飯。。。");
                doEat();
                //放下筷子,思考
                right.putDown();
            }
            left.putDown();
        }
    }

    /**
     * 喫飯動作,模擬隨機停頓5秒鐘
     */
    private void doEat() {
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
        } catch (InterruptedException e) {
        }
    }

    /**
     * 哲學家思考
     */
    public void think() {
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));
        } catch (InterruptedException e) {
        }
    }

    @Override
    public void run() {
        for(;;){
            this.eat();
            this.think();
        }
    }
}
package JavaCoreThreadPatten.capter07.v1;

import java.util.ArrayList;
import java.util.List;

/**
 * 查看線程dump
 * Java stack information for the threads listed above:
 * ===================================================
 * "Thread-4":
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.eat(Philosophers.java:26)
 *         - waiting to lock <0x000000076f7de890> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         - locked <0x000000076f7de028> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.run(Philosophers.java:59)
 * "Thread-0":
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.eat(Philosophers.java:26)
 *         - waiting to lock <0x000000076f7e1fa0> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         - locked <0x000000076f7de890> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.run(Philosophers.java:59)
 * "Thread-1":
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.eat(Philosophers.java:26)
 *         - waiting to lock <0x000000076f7e2040> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         - locked <0x000000076f7e1fa0> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.run(Philosophers.java:59)
 * "Thread-2":
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.eat(Philosophers.java:26)
 *         - waiting to lock <0x000000076f7de010> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         - locked <0x000000076f7e2040> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.run(Philosophers.java:59)
 * "Thread-3":
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.eat(Philosophers.java:26)
 *         - waiting to lock <0x000000076f7de028> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         - locked <0x000000076f7de010> (a JavaCoreThreadPatten.capter07.v1.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v1.Philosophers.run(Philosophers.java:59)
 *
 *         可以看出來其實已經形成了一個環形鎖:產生了死鎖
 */
public class Test {
    public static void main(String[] args) {
        //五個哲學家,五支筷子
        List<Chopstick> chopsticks = new ArrayList<>();
        for(int i=0;i<5;i++){
            chopsticks.add(new Chopstick(i));
        }
        List<Philosophers> philosophers = new ArrayList<>();
        for(int i=0;i<5;i++){
            if(i<4){
                philosophers.add(new Philosophers(i,chopsticks.get(i),chopsticks.get(i+1)));
            }else{
                philosophers.add(new Philosophers(i,chopsticks.get(i),chopsticks.get(0)));
            }
        }
        philosophers.forEach(p->{
            p.start();
        });
    }
}

哲學家問題案例V2(jdk內部鎖):

package JavaCoreThreadPatten.capter07.v2;

/*
    筷子實體
 */
public class Chopstick {
    private final int id;
    private Status status = Status.PUT_DOWN;

    public Chopstick(int id) {
        this.id = id;
    }

    public void pickUp(){
        this.status = Status.PICKED_UP;
    }

    public void putDown(){
        this.status = Status.PUT_DOWN;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "id=" + id +
                '}';
    }

    private enum Status{
        PUT_DOWN,PICKED_UP;
    }
}
package JavaCoreThreadPatten.capter07.v2;

/**
 * 執行方法抽象函數
 */
public abstract class AbstractTest {
    protected final int initNum;
    protected final Chopstick[] chopsticks;
    protected final AbstractPhilosopher[] abstractPhilosophers;

    public AbstractTest(int initNum) {
        this.initNum = initNum;
        this.chopsticks = initChopstick();
        this.abstractPhilosophers = initPhilosopher();
    }

    /**
     * 初始化筷子
     * @return
     */
    protected Chopstick[] initChopstick(){
        Chopstick[] chopsticks = new Chopstick[initNum];
        for(int i=0;i<initNum;i++){
            chopsticks[i] = new Chopstick(i);
        }
        return chopsticks;
    }

    /**
     * 初始化哲學家,由子類進行實現
     * @return
     */
    protected abstract AbstractPhilosopher[] initPhilosopher();

    /**
     * 運行方法
     */
    protected final void run(){
        if(abstractPhilosophers==null || abstractPhilosophers.length<=0){
            System.err.println("初始化未完成");
        }
        for(AbstractPhilosopher abstractPhilosopher:abstractPhilosophers){
            abstractPhilosopher.start();
        }
    }
}

 

package JavaCoreThreadPatten.capter07.v2;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 哲學家抽象類
 */
public abstract class AbstractPhilosopher extends Thread {

    protected final int id;
    protected final Chopstick left;
    protected final Chopstick right;

    public AbstractPhilosopher(int id, Chopstick left, Chopstick right) {
        this.id = id;
        this.left = left;
        this.right = right;
    }

    public abstract void eat();

    public void think(){
        try {
            System.out.println("我是哲學家:"+id+",我開始思考。。。");
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        for(;;){
            eat();
            think();
        }
    }

    public void doEat(){
        try {
            System.out.println("我是哲學家:"+id+",我開始喫飯。。。");
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package JavaCoreThreadPatten.capter07.v2;

import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 導致死鎖的哲學家模型
 */
public class DeadlockingPhilosopher extends AbstractPhilosopher{

    public DeadlockingPhilosopher(int id, Chopstick left, Chopstick right) {
        super(id, left, right);
    }

    @Override
    public void eat() {
        synchronized (super.left){
            System.out.println(id+"拿起左手邊的筷子:"+left);
            super.left.pickUp();
            synchronized (super.right){
                System.out.println(id+"拿起右手邊的筷子:"+right+";開始喫飯啦。。");
                super.right.pickUp();
                try {
                    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
                } catch (InterruptedException e) {
                }
                super.right.putDown();
            }
            super.left.putDown();
        }
    }
}
package JavaCoreThreadPatten.capter07.v2;

import java.util.List;

/**
 * 哲學家死鎖問題模擬程序
 * Java stack information for the threads listed above:
 * ===================================================
 * "Thread-1":
 *         at JavaCoreThreadPatten.capter07.v2.DeadlockingPhilosopher.eat(DeadlockingPhilosopher.java:21)
 *         - waiting to lock <0x000000076bb24918> (a JavaCoreThreadPatten.capter07.v2.Chopstick)
 *         - locked <0x000000076bb28978> (a JavaCoreThreadPatten.capter07.v2.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v2.AbstractPhilosopher.run(AbstractPhilosopher.java:35)
 * "Thread-0":
 *         at JavaCoreThreadPatten.capter07.v2.DeadlockingPhilosopher.eat(DeadlockingPhilosopher.java:21)
 *         - waiting to lock <0x000000076bb28978> (a JavaCoreThreadPatten.capter07.v2.Chopstick)
 *         - locked <0x000000076bb24918> (a JavaCoreThreadPatten.capter07.v2.Chopstick)
 *         at JavaCoreThreadPatten.capter07.v2.AbstractPhilosopher.run(AbstractPhilosopher.java:35)
 *
 * Found 1 deadlock.
 *
 * 我們可以發現一個死鎖問題
 */
public class DiningPhilosopherTest extends AbstractTest{
    public DiningPhilosopherTest(int initNum) {
        super(initNum);
    }

    public static void main(String[] args) {
        AbstractTest abstractTest = new DiningPhilosopherTest(2);
        abstractTest.run();
    }

    @Override
    protected AbstractPhilosopher[] initPhilosopher() {
        DeadlockingPhilosopher[] deadlockingPhilosophers = new DeadlockingPhilosopher[initNum];
        for(int i=0;i<initNum;i++){
            deadlockingPhilosophers[i] = new DeadlockingPhilosopher(i,chopsticks[i],chopsticks[(i+1)%initNum]);
        }
        return deadlockingPhilosophers;
    }
}

哲學家問題V3(基於顯示鎖):

package JavaCoreThreadPatten.capter07.v2;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 顯示鎖導致可能出現死鎖問題的哲學家
 */
public class BuggyLckBasedPhilosopher extends AbstractPhilosopher{
    /**
     * 確保每個筷子有一個顯示鎖與之對應,則確保該類的每一個實例共享同一個鎖map
     */
    private static final ConcurrentHashMap<Chopstick, Lock> LOCK_MAP;
    static {
        LOCK_MAP = new ConcurrentHashMap<>();
    }

    public BuggyLckBasedPhilosopher(int id, Chopstick left, Chopstick right) {
        super(id, left, right);
        LOCK_MAP.putIfAbsent(left,new ReentrantLock());
        LOCK_MAP.putIfAbsent(right,new ReentrantLock());
    }

    @Override
    public void eat() {
        if(pickUpChopstick(left) && pickUpChopstick(right)){
            doEat();
        }
        putDownChopstick(right);
        putDownChopstick(left);
        think();
    }

    protected boolean pickUpChopstick(Chopstick chopstick){
        final Lock lock = LOCK_MAP.get(chopstick);
        try{
            lock.lock();
            System.out.println("哲學家"+id+"撿起筷子"+chopstick);
            chopstick.pickUp();
            return true;
        }catch (Exception e){
            lock.unlock();
        }
        return false;
    }

    protected void putDownChopstick(Chopstick chopstick){
        final Lock lock = LOCK_MAP.get(chopstick);
        try{
            System.out.println("哲學家"+id+"放下筷子"+chopstick);
            chopstick.putDown();
            lock.unlock();
        }catch (Exception e){
            lock.unlock();
        }
    }

    @Override
    public void run() {
        for(;;){
            eat();
        }
    }
}
package JavaCoreThreadPatten.capter07.v2;

/**
 * 顯示鎖
 * Found one Java-level deadlock:
 * =============================
 * "Thread-1":
 *   waiting for ownable synchronizer 0x000000076bb34200, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
 *   which is held by "Thread-0"
 * "Thread-0":
 *   waiting for ownable synchronizer 0x000000076bb342a0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
 *   which is held by "Thread-1"
 *
 * Java stack information for the threads listed above:
 * ===================================================
 * "Thread-1":
 *         at sun.misc.Unsafe.park(Native Method)
 *         - parking to wait for  <0x000000076bb34200> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
 *         at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
 *         at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
 *         at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
 *         at JavaCoreThreadPatten.capter07.v2.BuggyLckBasedPhilosopher.pickUpChopstick(BuggyLckBasedPhilosopher.java:38)
 *         at JavaCoreThreadPatten.capter07.v2.BuggyLckBasedPhilosopher.eat(BuggyLckBasedPhilosopher.java:27)
 *         at JavaCoreThreadPatten.capter07.v2.BuggyLckBasedPhilosopher.run(BuggyLckBasedPhilosopher.java:62)
 * "Thread-0":
 *         at sun.misc.Unsafe.park(Native Method)
 *         - parking to wait for  <0x000000076bb342a0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
 *         at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
 *         at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
 *         at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
 *         at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
 *         at JavaCoreThreadPatten.capter07.v2.BuggyLckBasedPhilosopher.pickUpChopstick(BuggyLckBasedPhilosopher.java:38)
 *         at JavaCoreThreadPatten.capter07.v2.BuggyLckBasedPhilosopher.eat(BuggyLckBasedPhilosopher.java:27)
 *         at JavaCoreThreadPatten.capter07.v2.BuggyLckBasedPhilosopher.run(BuggyLckBasedPhilosopher.java:62)
 *
 * Found 1 deadlock.
 * 出現死鎖
 */
public class BuggyLckBasedTest extends AbstractTest{

    public BuggyLckBasedTest(int initNum) {
        super(initNum);
    }

    public static void main(String[] args) {
        AbstractTest abstractTest = new BuggyLckBasedTest(2);
        abstractTest.run();
    }

    @Override
    protected AbstractPhilosopher[] initPhilosopher() {
        BuggyLckBasedPhilosopher[] buggyLckBasedPhilosophers = new BuggyLckBasedPhilosopher[initNum];
        for(int i=0;i<initNum;i++){
            buggyLckBasedPhilosophers[i] = new BuggyLckBasedPhilosopher(i,chopsticks[i],chopsticks[(i+1)%initNum]);
        }
        return buggyLckBasedPhilosophers;
    }
}

死鎖產生的必要條件:

  • 資源互斥。涉及的資源必須是獨佔的,即每個資源一次只能夠被一個線程使用。
  • 資源不可搶奪。涉及的資源只能夠被其持有者主動釋放,而無法被資源的持有者和申請者之外的第三方線程所搶奪。
  • 佔用並等待資源。涉及的線程當前至少持有一個資源(資源A)並申請其他資源(資源B),而這些資源(資源B)恰好被其他線程持有。在這個資源等待的過程中,線程並不釋放其已經持有的資源。
  • 循環等待資源。涉及的線程必須在等待別的線程持有的資源,而這些線程又反過來在等待第一個線程所持有的資源。即獲取資源的順序是反向的。

產生死鎖的代碼特徵就是在持有一個鎖的情況下去申請另外一個鎖,這通常意味着鎖的嵌套。

一個線程在已經持有一個鎖的情況下再次申請這個鎖並不會導致死鎖,這是因爲java中的鎖都是可重入的,這種情況下線程再次申請這個鎖是可以成功的。

一般解決死鎖從消除“佔用並等待資源”和消除“循環等待資源”兩個方向入手。

解決死鎖的方式:

1.鎖粗化:使用粗粒度的鎖代替多個鎖,這樣涉及的線程都只需要申請一個鎖從而避免了死鎖,案例改造如下:

package JavaCoreThreadPatten.capter07.v2;

import java.util.Objects;

/**
 * 通過粗粒度的鎖規避死鎖
 */
public class GlobalLckBasedPhilosopher extends AbstractPhilosopher{
    private static final Object LOCK = new Object();

    public GlobalLckBasedPhilosopher(int id, Chopstick left, Chopstick right) {
        super(id, left, right);
    }

    @Override
    public void eat() {
        synchronized (LOCK){
            System.out.println("獲取到全局鎖");
            left.pickUp();
            right.pickUp();
            doEat();
            right.putDown();
            left.putDown();
            System.out.println("哲學家"+id+"喫飯完畢");
        }
        think();
    }
}
package JavaCoreThreadPatten.capter07.v2;

/**
 * 粗粒度鎖解決死鎖問題:實際上大大降低了併發性,轉變成了串行
 */
public class GlobalLockBasedTest extends AbstractTest{
    public GlobalLockBasedTest(int initNum) {
        super(initNum);
    }

    public static void main(String[] args) {
        AbstractTest abstractTest = new GlobalLockBasedTest(2);
        abstractTest.run();
    }

    @Override
    protected AbstractPhilosopher[] initPhilosopher() {
        GlobalLckBasedPhilosopher[] globalLckBasedPhilosophers = new GlobalLckBasedPhilosopher[initNum];
        for(int i=0;i<initNum;i++){
            globalLckBasedPhilosophers[i] = new GlobalLckBasedPhilosopher(i,chopsticks[i],chopsticks[(i+1)%initNum]);
        }
        return globalLckBasedPhilosophers;
    }
}

鎖粗話明顯地降低了併發性,導致資源的浪費。

2.鎖排序法:相關線程使用全局統一的順序申請鎖,確定獲取鎖的順序,消除循環等待資源的問題,避免死鎖。一般使用對象的hashcode(System.identityHashCode(Object))來作爲資源的排序依據。具體案例如下:

package JavaCoreThreadPatten.capter07.v2;

/**
 * 使用鎖排序規避死鎖:總是從小到大獲取鎖
 */
public class FixedPhilosopher extends AbstractPhilosopher{
    private Chopstick one;
    private Chopstick other;
    public FixedPhilosopher(int id, Chopstick left, Chopstick right) {
        super(id,left,right);
        if(System.identityHashCode(left) > System.identityHashCode(right)){
            Chopstick mid = left;
            one = right;
            other = mid;
        }else{
            one = left;
            other = right;
        }
    }

    @Override
    public void eat() {
        synchronized (one){
            System.out.println(id+"拿起第一根筷子:"+one);
            one.pickUp();
            synchronized (other){
                System.out.println(id+"拿起另外一根筷子:"+other+";開始喫飯啦。。");
                other.pickUp();
                doEat();
                other.putDown();
            }
            one.putDown();
        }
        think();
    }
}
package JavaCoreThreadPatten.capter07.v2;

/**
 * 通過按照鎖的hashCode順序來獲取鎖,解決死鎖問題,因爲都是從小到大獲取鎖,
 * 按照統一的順序申請鎖,消除循環等待資源問題
 */
public class FixedPhiosopherTest extends AbstractTest{
    public FixedPhiosopherTest(int initNum) {
        super(initNum);
    }

    public static void main(String[] args) {
        AbstractTest abstractTest = new FixedPhiosopherTest(2);
        abstractTest.run();
    }

    @Override
    protected AbstractPhilosopher[] initPhilosopher() {
        FixedPhilosopher[] fixedPhilosophers = new FixedPhilosopher[initNum];
        for(int i=0;i<initNum;i++){
            fixedPhilosophers[i] = new FixedPhilosopher(i,chopsticks[i],chopsticks[(i+1)%initNum]);
        }
        return fixedPhilosophers;
    }
}

3.使用ReetrantLock.tryLock(long,TimeUnit)申請鎖,避免一個線程無限制地等待另外一個線程持有的資源,從而消除“佔用並等待資源”這個條件,案例如下:

package JavaCoreThreadPatten.capter07.v2;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class FixedLockBasedPhiosopher extends AbstractPhilosopher{

    private static final ConcurrentHashMap<Chopstick, ReentrantLock> LOCK_MAP = new ConcurrentHashMap<>();

    public FixedLockBasedPhiosopher(int id, Chopstick left, Chopstick right) {
        super(id, left, right);
        LOCK_MAP.putIfAbsent(left,new ReentrantLock());
        LOCK_MAP.putIfAbsent(right,new ReentrantLock());
    }

    @Override
    public void eat() {
        ReentrantLock leftLock = LOCK_MAP.get(left);
        ReentrantLock rightLock = LOCK_MAP.get(right);
        try {
            boolean b = leftLock.tryLock(3, TimeUnit.SECONDS);
            if(b){
                System.out.println("哲學家"+id+"獲取到左邊的鎖,嘚瑟一下");
                left.pickUp();
                b = rightLock.tryLock(3,TimeUnit.SECONDS);
                if(b){
                    right.pickUp();
                    System.out.println("哲學家"+id+"獲取到又邊的鎖,喫飯。。。");
                    doEat();
                    rightLock.unlock();
                }
                leftLock.unlock();
            }
        } catch (InterruptedException e) {
            if(leftLock.isLocked()){
                leftLock.unlock();
            }
            if(rightLock.isLocked()){
                rightLock.unlock();
            }
        }
    }

    @Override
    public void run() {
        for(;;){
            eat();
            think();
        }
    }
}
package JavaCoreThreadPatten.capter07.v2;

/**
 * 使用ReentrantLock.tryLock(timeout,timeUnit)來解決死鎖問題
 */
public class FixedLockBasedTest extends AbstractTest{
    public FixedLockBasedTest(int initNum) {
        super(initNum);
    }

    public static void main(String[] args) {
        AbstractTest abstractTest = new FixedLockBasedTest(2);
        abstractTest.run();
    }

    @Override
    protected AbstractPhilosopher[] initPhilosopher() {
        FixedLockBasedPhiosopher[] fixedLockBasedPhiosophers = new FixedLockBasedPhiosopher[initNum];
        for(int i=0;i<initNum;i++){
            fixedLockBasedPhiosophers[i] = new FixedLockBasedPhiosopher(i,chopsticks[i],chopsticks[(i+1)%initNum]);
        }
        return fixedLockBasedPhiosophers;
    }
}

4.外部調用導致的死鎖。還有一種常見的情況是一個方法在持有一個鎖的情況下調用一個外部方法。假設類A有兩個同步方法syncA和syncB,類B有兩個同步方法syncC和syncD,syncA會調用syncC,syncD會調用syncB。這裏,syncA和syncD這兩個方法雖然不直接具備死鎖特徵,但是由於他們調用了一個外部方法,而這個方法是一個同步方法,因此這兩個方法實際上具備了死鎖特徵。當一個線程在執行A.syncA時,另外一個線程正在執行B.syncD,那麼這兩個線程就有可能產生死鎖。一般地,一個方法在持有一個鎖的情況下調用一個外部方法,而外部方法往往不在我們的控制範圍之內,其自身可能不會申請另外一個鎖,也可能會申請另外一個鎖,因此一般針對這種情況我們使用開放調用來規避,就是在一個方法在調用外部方法的時候不持有任何鎖。

規避死鎖的常見方式:

  • 粗鎖發--使用一個粗粒度的鎖代替多個鎖
  • 鎖排序法--相關線程使用全局統一的順序申請鎖
  • 使用ReentranLock.tryLock(long,TimeUnit)來申請鎖
  • 使用開放調用--在調用外部方法時不加鎖
  • 使用鎖的替代品(無狀態對象、線程持有對象、volatile關鍵字等),這些能避免鎖的開銷

死鎖的恢復

由於導致死鎖的線程的不可控性,因此死鎖恢復的實際可操作性並不強:對死鎖進行的故障恢復嘗試可能是徒勞的(故障線程可無法響應中斷)且有害的(可能導致活鎖等問題)

死鎖的自動恢復有賴於線程的中斷機制,其基本思想是:定義一個工作者線程專門用於死鎖檢測與恢復。該線程定期檢測系統中是否存在死鎖,若檢測到死鎖,則隨機選取一個死鎖線程並給其發送中斷。該中斷使得一個任意的死鎖線程被java虛擬機喚醒,從而使其拋出InterruptedException異常。這使得目標線程不再等待它本身永遠無法申請到的資源,從而破壞了死鎖產生的必要條件中的“佔用並等待資源”中的等待資源部分。目標線程則通過對InterruptedException進行處理的方式來響應中斷:目標線程捕獲InterruptedException異常後將其已經持有的資源主動釋放掉,這相當於破壞了死鎖產生的必要條件中的“佔用並等待資源”中的佔用資源部分。接着線程繼續監測系統是否仍然存在死鎖,若存在,則繼續選中一個任意的死鎖線程並給其發送中斷,直到系統中不再存在死鎖。

解決死鎖是通過java.lang.management.ThreadMXBean.findDeadlockedThreads()調用來實現死鎖檢測的。ThreadMXBean.findDeadlockedThreads()能夠返回一組死鎖線程的線程編號。ThreadMXBean類是JMX API的一部分,因此其提供的功能也可以通過jconsole、jvisualvm手工調用。

線程飢餓是指線程一直無法獲得其所需的資源而導致其任務一直無法進展的一種活性故障。

線程飢餓的一個典型例子是在高爭用的環境性使用非公平模式的讀寫鎖。

活鎖是指線程一直處於運行狀態,但是其任務卻一直無法進展的一種活性故障。

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章