全网最详细并发编程(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 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题

下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题:
在这里插入图片描述
顺序加锁的解决方案
在这里插入图片描述
在这里插入图片描述


欢迎关注公众号Java技术大本营,会不定期分享BAT面试资料等福利。

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值