什么是活鎖和飢餓?


活鎖

任務沒有被阻塞,由於某些條件沒有滿足,導致一直重復嘗試—失敗—嘗試—失敗的過程。 處於活鎖的實體是在不斷的改變狀態,活鎖有可能自行解開。

死鎖是大家都拿不到資源都占用着對方的資源,而活鎖是拿到資源卻又相互釋放不執行。

 

解決活鎖的一個簡單辦法就是在下一次嘗試獲取資源之前,隨機休眠一小段時間。

 

看一下,我們之前的一個例子,如果最后不進行隨機休眠,就會產生活鎖,現象就是很長一段時間,兩個線程都在不斷嘗試獲取和釋放鎖。

package constxiong.concurrency.a023;

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

/**
 * 測試 占有部分資源的線程進一步申請其他資源時,如果申請不到,主動釋放它占有的資源,破壞 "不可搶占" 條件
 * @author ConstXiong
 * @date 2019-09-24 14:50:51
 */
public class TestBreakLockOccupation {
    
    private static Random r = new Random(); 

    private static Lock lock1 = new ReentrantLock();
    
    private static Lock lock2 = new ReentrantLock();
    
    public static void main(String[] args) {
        new Thread(() -> {
            //標識任務是否完成
            boolean taskComplete = false;
            while (!taskComplete) {
                lock1.lock();
                System.out.println("線程:" + Thread.currentThread().getName() + " 獲取鎖 lock1 成功");
                try {
                    //隨機休眠,幫助造成死鎖環境
                    try {
                        Thread.sleep(r.nextInt(30));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                    //線程 0 嘗試獲取 lock2
                    if (lock2.tryLock()) {
                        System.out.println("線程:" + Thread.currentThread().getName() + " 獲取鎖 lock2 成功");
                        try {
                            taskComplete = true;
                        } finally {
                            lock2.unlock();
                        }
                    } else {
                        System.out.println("線程:" + Thread.currentThread().getName() + " 獲取鎖 lock2 失敗");
                    }
                } finally {
                    lock1.unlock();
                }
                
                //隨機休眠,避免出現活鎖
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        new Thread(() -> {
            //標識任務是否完成
            boolean taskComplete = false;
            while (!taskComplete) {
                lock2.lock();
                System.out.println("線程:" + Thread.currentThread().getName() + " 獲取鎖 lock2 成功");
                try {
                    //隨機休眠,幫助造成死鎖環境
                    try {
                        Thread.sleep(r.nextInt(30));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    
                    //線程2 嘗試獲取鎖 lock1
                    if (lock1.tryLock()) {
                        System.out.println("線程:" + Thread.currentThread().getName() + " 獲取鎖 lock1 成功");
                        try {
                            taskComplete = true;
                        } finally {
                            lock1.unlock();
                        }
                    } else {
                        System.out.println("線程:" + Thread.currentThread().getName() + " 獲取鎖 lock1 失敗");
                    }
                } finally {
                    lock2.unlock();
                }
                
                //隨機休眠,避免出現活鎖
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
    
}

 

 

飢餓

一個線程因為 CPU 時間全部被其他線程搶占而得不到 CPU 運行時間,導致線程無法執行。

產生飢餓的原因:

  • 優先級線程吞噬所有的低優先級線程的 CPU 時間
  • 其他線程總是能在它之前持續地對該同步塊進行訪問,線程被永久堵塞在一個等待進入同步塊
  • 其他線程總是搶先被持續地獲得喚醒,線程一直在等待被喚醒
package constxiong.concurrency.a024;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 測試線程飢餓
 * @author ConstXiong
 */
public class TestThreadHungry {

    private static ExecutorService es = Executors.newSingleThreadExecutor();
    
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Future<String> future1 = es.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("提交任務1");
                Future<String> future2 = es.submit(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        System.out.println("提交任務2");
                        return "任務 2 結果";
                    }
                });
                return future2.get();
            }
        });
        System.out.println("獲取到" + future1.get());
    }
    
}

 

 

打印結果如下,線程池卡死。線程池只能容納 1 個任務,任務 1 提交任務 2,任務 2 永遠得不到執行。

提交任務1

 

  

來一道刷了進BAT的面試題?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM