Java線程同步問題--哲學家就餐
1.場景
有五位沉默的哲學家圍坐在一張圓桌旁,他們一生都在吃東西和思考。
有五只筷子供他們使用,哲學家需要雙手拿到一雙筷子之后才能吃飯;吃完后會將筷子放下繼續(xù)思考。
那么現(xiàn)在有一個問題,我們需要想出一種方案,如何保證哲學家們可以交替吃飯和思考,而不會被餓死。
上面這個問題是由Dijkstra提出的一個經典的線程同步問題。
2.解決方案
我們在開始想如何解決問題之前,可以先將這個場景通過代碼還原,在程序中進行建模。
每一只筷子可以看做是一個資源數(shù)據(jù),都可以被它兩邊的哲學家嘗試去獲取,并且同一時間只能由其中一人持有,這可以通過我們JUC
包中的信號量Semaphore
來表示。
然后,每個哲學家可以看做是一個線程,每個線程中的run方法內容都是先進行思考,然后試圖獲取左右兩邊的筷子吃飯,吃完飯后繼續(xù)思考。
通過上面的分析,我們的代碼實現(xiàn)如下:
/** ?* @author 小黑說Java ?* @ClassName DiningPhilosophers ?* @Description 哲學家就餐問題 ?* @date 2022/2/6 ?**/ @Slf4j public class DiningPhilosophers implements Runnable { ? ? private final int id; ? ? public DiningPhilosophers(int id) { ? ? ? ? this.id = id; ? ? } ? ? private static final Random random = new Random(System.currentTimeMillis()); ? ? private static final Semaphore[] forks = new Semaphore[5]; ? ? // 初始化信號量,每個信號量為1,代表1只筷子 ? ? static { ? ? ? ? forks[0] = new Semaphore(1); ? ? ? ? forks[1] = new Semaphore(1); ? ? ? ? forks[2] = new Semaphore(1); ? ? ? ? forks[3] = new Semaphore(1); ? ? ? ? forks[4] = new Semaphore(1); ? ? } ? ? @Override ? ? public void run() { ? ? ? ? try { ? ? ? ? ? ? while (true) { ? ? ? ? ? ? ? ? think(); ? ? ? ? ? ? ? ? eat(id); ? ? ? ? ? ? } ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? log.error("異常中斷", e); ? ? ? ? } ? ? } ? ? /** ? ? ?* 哲學家思考隨機時間 ? ? ?*/ ? ? private void think() throws InterruptedException { ? ? ? ? TimeUnit.MILLISECONDS.sleep(random.nextInt(100)); ? ? } ? ? private void eat(int id) { ? ? ? ? // TODO ? ? } }
接下來,我們思考一下,如何實現(xiàn)哲學家吃飯的邏輯。
當一個哲學家需要吃飯時,他要拿起左右兩邊的筷子。
所以:
- 哲學家 A(0) 需要筷子0 和 4
- 哲學家 B(1) 需要筷子 1 和 0
- 哲學家 C(2) 需要筷子 2 和 1
- 哲學家 D(3) 需要筷子 3 和 2
- 哲學家 E(4) 需要筷子 4 和 3
所以每個哲學家線程都應該有個編號,所以我在DiningPhilosophers
中定義了屬性id表示哲學家的編號。
在吃飯方法中,需要根據(jù)id來決定獲取哪只筷子。
左手邊的筷子可以有用fork[id]
表示;
右手邊的筷子用fork[(id+4)%5]
表示。
那么我們的eat方法的實現(xiàn)如下:
private void eat(int id) throws InterruptedException { ? ? // 先拿左邊的筷子 ? ? forks[id].acquire(); ? ? // 然后拿右邊的筷子 ? ? forks[(id + 4) % 5].acquire(); ? ? // 吃一口飯 ? ? log.info("哲學家{}正在吃飯~", id); ? ? // 依次放下左邊的筷子和右邊的筷子 ? ? forks[id].release(); ? ? forks[(id + 4) % 5].release(); }
我們接著來測試我們的完整代碼。
/** ?* @author 小黑說Java ?* @ClassName DiningPhilosophers ?* @Description 哲學家就餐問題 ?* @date 2022/2/6 ?**/ @Slf4j public class DiningPhilosophers implements Runnable { ? ? private final int id; ? ? public DiningPhilosophers(int id) { ? ? ? ? this.id = id; ? ? } ? ? private static final Random random = new Random(System.currentTimeMillis()); ? ? private static final Semaphore[] forks = new Semaphore[5]; ? ? // 初始化信號量,每個信號量為1,代表1只筷子 ? ? static { ? ? ? ? forks[0] = new Semaphore(1); ? ? ? ? forks[1] = new Semaphore(1); ? ? ? ? forks[2] = new Semaphore(1); ? ? ? ? forks[3] = new Semaphore(1); ? ? ? ? forks[4] = new Semaphore(1); ? ? } ? ? @Override ? ? public void run() { ? ? ? ? try { ? ? ? ? ? ? while (true) { ? ? ? ? ? ? ? ? think(); ? ? ? ? ? ? ? ? eat(id); ? ? ? ? ? ? } ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? log.error("異常中斷", e); ? ? ? ? } ? ? } ? ? /** ? ? ?* 哲學家思考隨機時間 ? ? ?*/ ? ? private void think() throws InterruptedException { ? ? ? ? TimeUnit.MILLISECONDS.sleep(random.nextInt(100)); ? ? } ? ? private void eat(int id) throws InterruptedException { ? ? ? ? // 先拿左邊的筷子 ? ? ? ? forks[id].acquire(); ? ? ? ? // 然后拿右邊的筷子 ? ? ? ? forks[(id + 4) % 5].acquire(); ? ? ? ? // 吃一口飯 ? ? ? ? log.info("哲學家{}正在吃飯~", id); ? ? ? ? // 依次放下左邊的筷子和右邊的筷子 ? ? ? ? forks[id].release(); ? ? ? ? forks[(id + 4) % 5].release(); ? ? } ? ? public static void main(String[] args) { ? ? ? ? for (int i = 0; i < 5; i++) { ? ? ? ? ? ? new Thread(new DiningPhilosophers(i)).start(); ? ? ? ? } ? ? } }
運行上面的代碼后,會發(fā)現(xiàn)程序在運行一段時間后會進入死鎖狀態(tài)。
這種情況是因為,在某一時刻,所有的哲學家都獲取到了左手邊的筷子,而無法獲取到右手邊的筷子,導致沒有人可以到東西,陷入僵局。
該如何避免出現(xiàn)這種死鎖問題呢?
方法一:限制吃飯的哲學家人數(shù)
很簡單的一種方法,就是在一個時間點,只能有最多4個哲學家開始吃飯。4個哲學家分5只筷子,則永遠不會發(fā)生死鎖。
要實現(xiàn)這種方法,我們可以再定義一個許可數(shù)為4的信號量Semaphore,表示剩余可以吃飯的哲學家名額。
代碼實現(xiàn)如下:
/** ?* @author 小黑說Java ?* @ClassName DiningPhilosophers ?* @Description 哲學家就餐問題 ?* @date 2022/2/6 ?**/ @Slf4j public class DiningPhilosophers implements Runnable { ? ? private final int id; ? ? public DiningPhilosophers(int id) { ? ? ? ? this.id = id; ? ? } ? ? private static final Random random = new Random(System.currentTimeMillis()); ? ? private static final Semaphore[] forks = new Semaphore[5]; ? ? private static final Semaphore maxDiners = new Semaphore(4); ? ? // 初始化信號量,每個信號量為1,代表1只筷子 ? ? static { ? ? ? ? forks[0] = new Semaphore(1); ? ? ? ? forks[1] = new Semaphore(1); ? ? ? ? forks[2] = new Semaphore(1); ? ? ? ? forks[3] = new Semaphore(1); ? ? ? ? forks[4] = new Semaphore(1); ? ? } ? ? @Override ? ? public void run() { ? ? ? ? try { ? ? ? ? ? ? while (true) { ? ? ? ? ? ? ? ? think(); ? ? ? ? ? ? ? ? eat(id); ? ? ? ? ? ? } ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? log.error("異常中斷", e); ? ? ? ? } ? ? } ? ? /** ? ? ?* 哲學家思考隨機時間 ? ? ?*/ ? ? private void think() throws InterruptedException { ? ? ? ? TimeUnit.MILLISECONDS.sleep(random.nextInt(100)); ? ? } ? ? private void eat(int id) throws InterruptedException { ? ? ? ? // 先獲得吃飯名額 ? ? ? ? maxDiners.acquire(); ? ? ? ? // 先拿左邊的筷子 ? ? ? ? forks[id].acquire(); ? ? ? ? // 然后拿右邊的筷子 ? ? ? ? forks[(id + 4) % 5].acquire(); ? ? ? ? // 吃一口飯 ? ? ? ? log.info("哲學家{}正在吃飯~", id); ? ? ? ? // 依次放下左邊的筷子和右邊的筷子 ? ? ? ? forks[id].release(); ? ? ? ? forks[(id + 4) % 5].release(); ? ? ? ? // 吃完之后歸還吃飯名額 ? ? ? ? maxDiners.release(); ? ? } ? ? public static void main(String[] args) { ? ? ? ? for (int i = 0; i < 5; i++) { ? ? ? ? ? ? new Thread(new DiningPhilosophers(i)).start(); ? ? ? ? } ? ? } }
方法二:找到一個左撇子哲學家
這種方法是讓其中一個哲學家和其他哲學家拿筷子的順序和其他哲學家不一樣。
比如其他人都是先拿右手邊再拿左手邊,而這個左撇子哲學家則先拿左手邊再拿右手邊。
而哪一位哲學家被選為左撇子并不重要,因為桌子是圓的,所以我們就選擇0號哲學家為左撇子。
代碼實現(xiàn)如下:
/** ?* @ClassName DiningPhilosophers ?* @Description 哲學家就餐問題 ?* @date 2022/2/6 ?**/ @Slf4j public class DiningPhilosophers implements Runnable { ? ? private final int id; ? ? public DiningPhilosophers(int id) { ? ? ? ? this.id = id; ? ? } ? ? private static final Random random = new Random(System.currentTimeMillis()); ? ? private static final Semaphore[] forks = new Semaphore[5]; ? ? // 初始化信號量,每個信號量為1,代表1只筷子 ? ? static { ? ? ? ? forks[0] = new Semaphore(1); ? ? ? ? forks[1] = new Semaphore(1); ? ? ? ? forks[2] = new Semaphore(1); ? ? ? ? forks[3] = new Semaphore(1); ? ? ? ? forks[4] = new Semaphore(1); ? ? } ? ? @Override ? ? public void run() { ? ? ? ? try { ? ? ? ? ? ? while (true) { ? ? ? ? ? ? ? ? think(); ? ? ? ? ? ? ? ? eat(id); ? ? ? ? ? ? } ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? log.error("異常中斷", e); ? ? ? ? } ? ? } ? ? /** ? ? ?* 哲學家思考隨機時間 ? ? ?*/ ? ? private void think() throws InterruptedException { ? ? ? ? TimeUnit.MILLISECONDS.sleep(random.nextInt(100)); ? ? } ? ? private void eat(int id) throws InterruptedException { ? ? ? ? if (id == 0) { ? ? ? ? ? ? hanleLeftFirst(id); ? ? ? ? } else { ? ? ? ? ? ? hanldRightFirst(id); ? ? ? ? } ? ? ? ? // 吃一口飯 ? ? ? ? log.info("哲學家{}正在吃飯~", id); ? ? ? ? forks[id].release(); ? ? ? ? forks[(id + 4) % 5].release(); ? ? } ? ? private void hanleLeftFirst(int id) throws InterruptedException { ? ? ? ? forks[id].acquire(); ? ? ? ? forks[(id + 4) % 5].acquire(); ? ? } ? ? private void hanldRightFirst(int id) throws InterruptedException { ? ? ? ? forks[(id + 4) % 5].acquire(); ? ? ? ? forks[id].acquire(); ? ? } ? ? public static void main(String[] args) { ? ? ? ? for (int i = 0; i < 5; i++) { ? ? ? ? ? ? new Thread(new DiningPhilosophers(i)).start(); ? ? ? ? } ? ? } }
到此這篇關于Java線程同步問題的文章就介紹到這了,更多相關Java線程同步內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java 實現(xiàn)取int型的第二個字節(jié)的數(shù)
這篇文章主要介紹了java 實現(xiàn)取int型的第二個字節(jié)的數(shù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01SpringBoot如何使用Fastjson解析Json數(shù)據(jù)
這篇文章主要介紹了SpringBoot如何使用Fastjson解析Json數(shù)據(jù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-03-03Java中使用WebUploader插件上傳大文件單文件和多文件的方法小結
這篇文章主要介紹了Java中使用WebUploader插件上傳大文件單文件和多文件的方法小結的相關資料,需要的朋友可以參考下2016-06-06java實現(xiàn)圖的鄰接表存儲結構的兩種方式及實例應用詳解
這篇文章主要介紹了java實現(xiàn)圖的鄰接表存儲結構的兩種方式及實例應用詳解,鄰接表構建圖是必須需要一個Graph對象,也就是圖對象!該對象包含屬性有:頂點數(shù)、邊數(shù)以及圖的頂點集合,需要的朋友可以參考下2019-06-06@Autowired注解注入的xxxMapper報錯問題及解決
這篇文章主要介紹了@Autowired注解注入的xxxMapper報錯問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Eclipse+Java+Swing實現(xiàn)圖書管理系統(tǒng)(詳細代碼)
這篇文章主要介紹了Eclipse+Java+Swing實現(xiàn)圖書管理系統(tǒng)并附上詳細代碼,需要的小伙伴可以參考一下,希望對你有所幫助2022-01-01