全網最詳細併發編程(3)---入門篇

全網最詳細併發編程(3)—入門篇

本文主要講解wait/notify的正確使用姿勢、park/unpark、join()的原理、模式之生產者-消費者模式(異步)、保護性暫停模式(同步)、線程狀態轉換的流程、死鎖和活鎖以及如何檢查死鎖等。

一、 wait notify

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
演示wait/notify
在這裏插入圖片描述
運行結果
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

二、wait/notify的正確使用姿勢

在這裏插入圖片描述
step 1

	static final Object room = new Object();
	static boolean hasCigarette = false;
	static boolean hasTakeout = false;
	//思考下面的解決方案好不好,爲什麼?
	new Thread(() -> {
		synchronized (room) {
			log.debug("有煙沒?[{}]", hasCigarette);
		if (!hasCigarette) {
			 log.debug("沒煙,先歇會!");
			 sleep(2);
		 }
		 log.debug("有煙沒?[{}]", hasCigarette);
		 if (hasCigarette) {
		 	log.debug("可以開始幹活了");
		 }
		 }
	}, "小南").start();
	
	for (int i = 0; i < 5; i++) {
		 new Thread(() -> {
			 synchronized (room) {
			 log.debug("可以開始幹活了");
		 }
	 }, "其它人").start();
	 }
	 
	sleep(1);
	new Thread(() -> {
		 // 這裏能不能加 synchronized (room)?
		 hasCigarette = true;
		 log.debug("煙到了噢!");
	}, "送煙的").start();

輸出

	20:49:49.883 [小南] c.TestCorrectPosture - 有煙沒?[false] 
	20:49:49.887 [小南] c.TestCorrectPosture - 沒煙,先歇會!
	20:49:50.882 [送煙的] c.TestCorrectPosture - 煙到了噢!
	20:49:51.887 [小南] c.TestCorrectPosture - 有煙沒?[true] 
	20:49:51.887 [小南] c.TestCorrectPosture - 可以開始幹活了
	20:49:51.887 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:49:51.887 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:49:51.888 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:49:51.888 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:49:51.888 [其它人] c.TestCorrectPosture - 可以開始幹活了

在這裏插入圖片描述
step 2
思考下面的實現行嗎,爲什麼?

new Thread(() -> {
	 synchronized (room) {
		 log.debug("有煙沒?[{}]", hasCigarette);
		 if (!hasCigarette) {
		 	log.debug("沒煙,先歇會!");
		 try {
		 room.wait(2000);
	 } catch (InterruptedException e) {
	 	e.printStackTrace();
	 }
	 }
	 log.debug("有煙沒?[{}]", hasCigarette);
	 if (hasCigarette) {
	 	log.debug("可以開始幹活了");
	 }
	 }
	}, "小南").start();
	
	for (int i = 0; i < 5; i++) {
		 new Thread(() -> {
		 synchronized (room) {
		 	log.debug("可以開始幹活了");
		 }
	 }, "其它人").start();
	}
	
	sleep(1);
	new Thread(() -> {
	 synchronized (room) {
		 hasCigarette = true;
		 	log.debug("煙到了噢!");
		 room.notify();
	 }
}, "送煙的").start();

輸出

	20:51:42.489 [小南] c.TestCorrectPosture - 有煙沒?[false] 
	20:51:42.493 [小南] c.TestCorrectPosture - 沒煙,先歇會!
	20:51:42.493 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:51:42.493 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:51:42.494 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:51:42.494 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:51:42.494 [其它人] c.TestCorrectPosture - 可以開始幹活了
	20:51:43.490 [送煙的] c.TestCorrectPosture - 煙到了噢!
	20:51:43.490 [小南] c.TestCorrectPosture - 有煙沒?[true] 
	20:51:43.490 [小南] c.TestCorrectPosture - 可以開始幹活了

在這裏插入圖片描述

new Thread(() -> {
	 synchronized (room) {
	 	log.debug("有煙沒?[{}]", hasCigarette);
	 if (!hasCigarette) {
	 	log.debug("沒煙,先歇會!");
	 try {
	 room.wait();
	 } catch (InterruptedException e) {
	 	e.printStackTrace();
	 }
	 }
	 log.debug("有煙沒?[{}]", hasCigarette);
	 if (hasCigarette) {
	 	log.debug("可以開始幹活了");
	 } else {
		log.debug("沒幹成活...");
	 }
	 }
	}, "小南").start();
	
new Thread(() -> {
	 synchronized (room) {
		 Thread thread = Thread.currentThread();
		 log.debug("外賣送到沒?[{}]", hasTakeout);
		 if (!hasTakeout) {
		 	log.debug("沒外賣,先歇會!");
		 try {
		 	room.wait();
	 } catch (InterruptedException e) {
	 e.printStackTrace();
	 	}
	 }
	 log.debug("外賣送到沒?[{}]", hasTakeout);
	 if (hasTakeout) {
	 	log.debug("可以開始幹活了");
	 } else {
	 	log.debug("沒幹成活...");
	 }
	 }
	}, "小女").start();
	
	sleep(1);
	new Thread(() -> {
		 synchronized (room) {
		 hasTakeout = true;
		 log.debug("外賣到了噢!");
		 room.notify();
	 }
}, "送外賣的").start();

在這裏插入圖片描述在這裏插入圖片描述
step4

new Thread(() -> {
 synchronized (room) {
	 hasTakeout = true;
	 log.debug("外賣到了噢!");
	 room.notifyAll();
 }
}, "送外賣的").start();

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

三、模式篇

1、保護性暫停
在這裏插入圖片描述

class GuardedObject {
	 private Object response;
	 private final Object lock = new Object();
	 public Object get() {
	 synchronized (lock) {
		// 條件不滿足則等待
		while (response == null) {
		try {
			lock.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} }
		return response; }
	 }
	 public void complete(Object response) {
		synchronized (lock) {
		// 條件滿足,通知等待線程
		this.response = response;
		lock.notifyAll();
		}
	 }
}

應用:一方等待另一方的返回結果(帶超時版 GuardedObject)

@Slf4j(topic = "c.TestGurad")
public class TestGuard {

    public static void main(String[] args) {
        GuardObject guardObject = new GuardObject();

        new Thread(() ->{
            //等待結果
            log.debug("等待結果");
            List<String> results = (List<String>)guardObject.get(2000);
//            log.debug("獲取結果完畢,結果大小爲:{}",results.size());
            log.debug("結果:{}",results);
        },"t1").start();

        new Thread(() -> {

            //執行下載
            log.debug("執行下載");
            List<String> downloaderData = null;
            try {
                downloaderData = Downloader.download();
                guardObject.produce(null);
//                log.debug("下載結束");

            } catch (IOException e) {
                e.printStackTrace();
            }

        },"t2").start();
    }

}

class GuardObject{
    //結果
    private Object response;

    //獲取結果
    public Object get(long timeout){
        synchronized (this){
            //記錄開始時間
            long start = System.currentTimeMillis();
            //記錄經歷的時間
            long passTime = 0;

            //沒有結果
            while (response == null) {
                //waitTime爲這一輪循環應該等待的時間
                long watiTime = timeout - passTime ;
                //經歷的時間超過超時時間就退出循環不再等待
                if(watiTime<0){
                    break;
                }
                try {
                    this.wait(watiTime); //避免虛假喚醒的情況,下一輪就不用等待timeout這麼多時間了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                passTime = System.currentTimeMillis() - start;
            }
            return response;
        }
    }

    //產生結果
    public void produce(Object response){
        synchronized (this){
            //給結果成員變量賦值
            this.response = response;
            this.notifyAll();
        }
    }
}

1.1、join()的原理
在這裏插入圖片描述
1.2多任務版GuardObject(一個線程對應一個GuardObject)
在這裏插入圖片描述
在這裏插入圖片描述

@Slf4j(topic = "c.Test20")
public class Test20 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }
        Sleeper.sleep(1);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "內容" + id).start();
        }
    }
}

@Slf4j(topic = "c.People")
class People extends Thread{
    @Override
    public void run() {
        // 收信
        GuardedObject guardedObject = Mailboxes.createGuardedObject();
        log.debug("開始收信 id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        log.debug("收到信 id:{}, 內容:{}", guardedObject.getId(), mail);
    }
}

@Slf4j(topic = "c.Postman")
class Postman extends Thread {
    private int id;
    private String mail;

    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
        log.debug("送信 id:{}, 內容:{}", id, mail);
        guardedObject.complete(mail);
    }
}

class Mailboxes {
    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();

    private static int id = 1;
    // 產生唯一 id
    private static synchronized int generateId() {
        return id++;
    }

    public static GuardedObject getGuardedObject(int id) {
        //根據id獲取到box並刪除對應的key和value,避免堆內存爆了
        return boxes.remove(id);
    }

    public static GuardedObject createGuardedObject() {
        GuardedObject go = new GuardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}

// 增加超時效果
class GuardedObject {

    // 標識 Guarded Object
    private int id;

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

    public int getId() {
        return id;
    }

    // 結果
    private Object response;

    // 獲取結果
    // timeout 表示要等待多久 2000
    public Object get(long timeout) {
        synchronized (this) {
            // 開始時間 15:00:00
            long begin = System.currentTimeMillis();
            // 經歷的時間
            long passedTime = 0;
            while (response == null) {
                // 這一輪循環應該等待的時間
                long waitTime = timeout - passedTime;
                // 經歷的時間超過了最大等待時間時,退出循環
                if (timeout - passedTime <= 0) {
                    break;
                }
                try {
                    this.wait(waitTime); // 虛假喚醒 15:00:01
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 求得經歷時間
                passedTime = System.currentTimeMillis() - begin; // 15:00:02  1s
            }
            return response;
        }
    }

    // 產生結果
    public void complete(Object response) {
        synchronized (this) {
            // 給結果成員變量賦值
            this.response = response;
            this.notifyAll();
        }
    }
}

2、生產者-消費者模式(異步模式)

@Slf4j(topic = "c.Test21")
public class Test21 {

    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);

        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id , "值"+id));
            }, "生產者" + i).start();
        }

        new Thread(() -> {
            while(true) {
                sleep(1);
                Message message = queue.take();
            }
        }, "消費者").start();
    }

}

// 消息隊列類 , java 線程之間通信
@Slf4j(topic = "c.MessageQueue")
class MessageQueue {
    // 消息的隊列集合
    private LinkedList<Message> list = new LinkedList<>();
    // 隊列容量
    private int capcity;

    public MessageQueue(int capcity) {
        this.capcity = capcity;
    }

    // 獲取消息
    public Message take() {
        // 檢查隊列是否爲空
        synchronized (list) {
            while(list.isEmpty()) {
                try {
                    log.debug("隊列爲空, 消費者線程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 從隊列頭部獲取消息並返回
            Message message = list.removeFirst();
            log.debug("已消費消息 {}", message);
            list.notifyAll();
            return message;
        }
    }

    // 存入消息
    public void put(Message message) {
        synchronized (list) {
            // 檢查對象是否已滿
            while(list.size() == capcity) {
                try {
                    log.debug("隊列已滿, 生產者線程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 將消息加入隊列尾部
            list.addLast(message);
            log.debug("已生產消息 {}", message);
            list.notifyAll();
        }
    }
}

final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

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

輸出結果:
在這裏插入圖片描述
2.1 park & unpark
在這裏插入圖片描述
輸出結果:
在這裏插入圖片描述
2.1.1 park/unpark與wait/notify的區別
在這裏插入圖片描述
2.1.2 park & unpark的原理
在這裏插入圖片描述
兩種情況:
① 先調用park,再調用unpark
在這裏插入圖片描述
在這裏插入圖片描述
② 先調用unpark,再調用park
在這裏插入圖片描述
2.1.3 重新理解線程之間的六種轉換狀態
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
2.1.4 多把鎖
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
改進

public class TestMultiLock {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        },"小南").start();
        new Thread(() -> {
            bigRoom.sleep();
        },"小女").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom {

    private final Object studyRoom = new Object();

    private final Object bedRoom = new Object();

    public void sleep() {
        synchronized (bedRoom) {
            log.debug("sleeping 2 小時");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (studyRoom) {
            log.debug("study 1 小時");
            Sleeper.sleep(1);
        }
    }

}

在這裏插入圖片描述
2.1.5 活躍性
在這裏插入圖片描述

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                log.debug("lock B");
                sleep(0.5);
                synchronized (A) {
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

結果:
在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
2.1.6 哲學家就餐問題
在這裏插入圖片描述

public class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("蘇格拉底", c1, c2).start();
        new Philosopher("柏拉圖", c2, c3).start();
        new Philosopher("亞里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c1, c5).start();
    }
}

@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 嘗試獲得左手筷子
            synchronized (left) {
                // 嘗試獲得右手筷子
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    Random random = new Random();
    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(1);
    }
}

class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

2.1.7 活鎖

活鎖出現在兩個線程互相改變對方的結束條件,最後誰也無法結束,例如

import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            // 期望減到 0 退出循環
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超過 20 退出循環
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

2.1.8 飢餓
很多教程中把飢餓定義爲,一個線程由於優先級太低,始終得不到 CPU 調度執行,也不能夠結束,飢餓的情況不易演示,講讀寫鎖時會涉及飢餓問題

下面我講一下我遇到的一個線程飢餓的例子,先來看看使用順序加鎖的方式解決之前的死鎖問題:
在這裏插入圖片描述
順序加鎖的解決方案
在這裏插入圖片描述
在這裏插入圖片描述

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