图文并茂,一篇长文带你搞懂并发编程

进程与线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

线程#

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

两者对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
  • 同一台计算机的进程通信称为 IPC(Inter-process communication)
  • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

并发与并行#

单核 cpu 下,线程实际还是 串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,一般会将这种 线程轮流使用 CPU 的做法称为并发

cpu时间片1时间片2时间片3
core线程1线程2线程3

image


多核 cpu下,每个 核(core)都可以调度运行线程,这时候线程可以是并行的。

cpu时间片1时间片2时间片3
core线程1线程2线程3
core线程2线程3线程1

image

  • 并发(concurrent)是同一时间应对(dealing with)多件事情的能力
  • 并行(parallel)是同一时间动手做(doing)多件事情的能力

同步和异步#

以调用方角度来讲,如果需要等待结果返回,才能继续运行就是同步,不需要等待结果返回,就能继续运行就是异步

image

多核cpu才能够提高效率,单核仍然是轮流执行,还多了线程切换的时间

  1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活
  2. 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的,有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任务都能拆分(参考后文的【阿姆达尔定律】)也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义
  3. IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化

Java线程#

本章内容#

  • 创建和运行线程
  • 查看线程
  • 线程 API
  • 线程状态

创建核运行线程#

       // 最常规的方式,不推荐
        new Thread("thread1"){
            @Override
            public void run() {
                System.out.println("我是另外一个线程");
            }
        }.start();
        System.out.println("你好啊啊哈哈哈Z");
        TimeUnit.SECONDS.sleep(1);
        System.out.println("啊哈哈哈");
       //线程和任务分开,更加灵活
       Runnable runable = ()->log.info("我是b线程");
       new Thread(runable,"t2").start();
       log.info("我是主线程");

        FutureTask<String> futureTask = new FutureTask<>(()->{
            log.info("我是另外一个线程");
            TimeUnit.SECONDS.sleep(3);
            return "啊哈哈哈";
        });
        new Thread(futureTask,"t1").start();
        log.info("我是主线程");
        // 间接实现了future接口,所以可以
        log.info(futureTask.get());

image

线程上下文切换#

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
切换的原因

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
    当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念
  • 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

    image

     

    image

相关方法解析

run和start

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
    sleep和yield
  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞),此时任务调度器不会分配时间片给该线程
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
    yield意思为礼让一下
  5. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行线程,此时任务调度器可以分配时间片给该线程
  6. 具体的实现依赖于操作系统的任务调度器
    可以通过thread.setPriority(Thread.MAX_PRIORITY)设置线程优先级,在cpu繁忙的时候,设置了优先级会有更大的机会优先执行,空闲的时候没有什么作用
        Runnable task1 = () -> {
            int count = 0;
            for (; ; ) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (; ; ) {
        // Thread.yield();
                System.out.println(" ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        // t1.setPriority(Thread.MIN_PRIORITY);
        // t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();

sleep可以避免cpu空转导致cpu被占满

join方法详解#

static int r = 10;
    @Test
    public void test() throws InterruptedException {
        Thread thread = new Thread(() -> {
            log.info("线程开始更改r变量");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 100;
        });
        thread.start();
        thread.join();
        log.info("r的值为{}", r);
    }

如下代码,如果不实用join,此时r的直还没有更改,显示的还是更改前的数值。
join的作用为等待某个线程运行结束
多个join情况

 @Test
    public void test() throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                log.info("t1开始");
                TimeUnit.SECONDS.sleep(1);
                log.info("t1结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        Thread thread1 = new Thread(() -> {
            try {
                log.info("t2开始");
                TimeUnit.SECONDS.sleep(2);
                log.info("t2结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t2");
        log.info("主线程开始");
        thread.start();
        thread1.start();
        thread.join();
        thread1.join();
        log.info("主线程结束");
    }

相关结果

image


只需要等待两秒
如果颠倒两个join,示意图如下

image


有时效的join

        Thread thread = new Thread(() -> {
            try {
                log.info("t1开始");
                TimeUnit.SECONDS.sleep(2);
                log.info("t1结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        log.info("主线程开始");
        thread.start();
        thread.join(1);
        log.info("主线程结束");

结果如下

image


主线程结束那么就结束了。如果没等够结束继续向下运行。

interrupt 方法详解#

sleep,join,wait(后面介绍)会使该线程处于阻塞状态,处于改状态的线程cpu将不会分配时间片给该线程,这也是sleep可以降低cpu的使用率的原因。处于阻塞状态下的线程可以被打断,此时会抛出异常。打断处于阻塞状态下的线程,此时不会处理打断状态,打断正常运行下的线程,打断标记会置为真,可以用来优雅的停止线程

	@Test
    public void test()  {
        Thread thread = new Thread(() -> {
            sleep(2);
        },"t1");
        log.info("主线程开始");
        thread.start();
        sleep(1);
        thread.interrupt();
        log.info(thread.isInterrupted()+"");
        log.info("主线程结束");
    }

image


需要在线程中自己处理打断的状态,代码如下

两阶段终止模式(在t1线程中优雅的停止t2线程)#

image

        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    if (Thread.currentThread().isInterrupted()){
                        log.info("我被打断了(▰˘︹˘▰)");
                        break;
                    }
                    log.info("执行监控");
                    sleep(2);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        },"t1");
        thread.start();
        sleep(5);
        log.info("主线程打断监控线程");
        thread.interrupt();
        sleep(3);
        log.info("主线程结束");

image

    @Test
    public void test() throws InterruptedException {
        // 处于这个状态也是阻塞
        // 当被打算后打断标志为真
        Thread thread = new Thread(()->{
            log.info("开始打断");
            LockSupport.park();
           
            log.info("打断标志{}",Thread.currentThread().isInterrupted());
            // 当打断标志为真的时候再次执行park时效,打断标志为加的时候才有效, 
            // Thread.interrupted()可以清除打断标志
            LockSupport.park();
            log.info("啦啦啦");
        });
        thread.start();
        sleep(2);
        thread.interrupt();
        sleep(1);
    }

image

守护线程#

image


我在本地实验没有达到这个效果。main线程结束后就没有下文了。

线程的状态#

image

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的
  • 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
  • TERMINATED 当线程代码运行结束
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
        System.in.read();

线程的6种状态代码示例如上
多线程统筹的事例

image

        Thread t1 = new Thread(()->{
            log.info("洗水壶");
            sleep(1);
            log.info("烧开水");
            sleep(15);
        },"t1");
        Thread t2 = new Thread(()->{
            log.info("洗茶壶");
            sleep(1);
            log.info("洗茶杯");
            sleep(2);
            log.info("拿茶叶");
            sleep(1);
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("泡茶");

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

image


需要花费16秒

共享模型之管程#

本章内容#

  • 共享问题
  • synchronized
  • 线程安全分析
  • Monitor
  • wait/notify
  • 线程状态转换
  • 活跃性
  • Lock

    image

     

    image

     

    image

     

    image


    多线程操作共享资源此时会有线程安全的问题
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                n++;
            }
        },"t1");
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 500; i++) {
                n--;
            }

        },"t2");
        t1.start();
        t2.start();
        t2.join();
        t1.join();
        log.info("最终n的结果为{}",n);

发现最终n的结果不是0

image

在多个线程对共享资源读写操作时发生指令交错,就会出现问题

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
既有读又有写
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量
    当一个对象拥有锁的时候,其他线程会进入到阻塞状态,获取锁的执行完毕会唤醒拿不到该锁阻塞的线程
    synchronized (Demo01.class){n++;}

    image

  • synchronized(对象) 中的对象,可以想象为一个房间(room),有唯一入口(门)房间只能一次进入一人进行计算,线程 t1,t2 想象成两个人
  • 当线程 t1 执行到 synchronized(room) 时就好比 t1 进入了这个房间,并锁住了门拿走了钥匙,在门内执行count++ 代码
  • 这时候如果 t2 也运行到了 synchronized(room) 时,它发现门被锁住了,只能在门外等待,发生了上下文切换,阻塞住了
  • 这中间即使 t1 的 cpu 时间片不幸用完,被踢出了门外(不要错误理解为锁住了对象就能一直执行下去哦),
  • 这时门还是锁住的,t1 仍拿着钥匙,t2 线程还在阻塞状态进不来,只有下次轮到 t1 自己再次获得时间片时才能开门进入
  • 当 t1 执行完 synchronized{} 块内的代码,这时候才会从 obj 房间出来并解开门上的锁,唤醒t2 线程把钥匙给他。t2 线程这时才可以进入 obj 房间,锁住了门拿上钥匙,执行它的 count-- 代码

    image


    synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
    改造
    更符合面相对象线程操作资源类的思想
    @Test
    public  void test() throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                room.increment();
            }
        },"t1");
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 500; i++) {
                room.decrement();
            }

        },"t2");
        t1.start();
        t2.start();
        t2.join();
        t1.join();
        log.info("最终n的结果为{}",room.get());
    }

    class Room {
        int value = 0;
        public void increment() {
            synchronized (this) {
                value++;
            }
        }
        public void decrement() {
            synchronized (this) {
                value--;
            }
        }
        public int get() {
            synchronized (this) {
                return value;
            }
        }

        public Room() {
        }
    }

静态方法中synchronized等价于锁是该类,非静态方法锁是该对象的实例

线程安全问题#

  1. 如果它们没有共享,则线程安全
  2. 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
    • 如果只有读操作,则线程安全
    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全
  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果该对象逃离方法的作用范围,需要考虑线程安全
      成员变量的线程安全问题
class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            method2();
            method3();
        }
    }

    private void method2() {
        list.add("1");
    }

    private void method3() {
        list.remove(0);
    }

原因:多个线程添加的时候有可能只添加成功一次,多个线程执行删除的时候执行了多次
list.add不是原子操作
常见的线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类
    线程安全指的是多个线程调用他们类的方法是安全的。即可以理解为他们的方法都是原子性的。但是组合起来不是原子的
    开闭原则中不想让子类覆盖的方法可以将该类设置成为final,如string
    多人买票线程代码分析
public class ExerciseSell {
    public static void main(String[] args) throws InterruptedException {
        // 模拟多人买票
        TicketWindow window = new TicketWindow(1000);

        // 所有线程的集合
        List<Thread> threadList = new ArrayList<>();
        // 卖出的票数统计
        List<Integer> amountList = new Vector<>();
        for (int i = 0; i < 2000; i++) {
            Thread thread = new Thread(() -> {
                // 买票
                int amount = window.sell(random(5));
                // 统计买票数
                amountList.add(amount);
            });
            threadList.add(thread);
            thread.start();
        }

        for (Thread thread : threadList) {
            thread.join();
        }

        // 统计卖出的票数和剩余票数
        log.debug("余票:{}",window.getCount());
        log.debug("卖出的票数:{}", amountList.stream().mapToInt(i-> i).sum());
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~5
    public static int random(int amount) {
        return random.nextInt(amount) + 1;
    }
}

// 售票窗口
class TicketWindow {
    private int count;

    public TicketWindow(int count) {
        this.count = count;
    }

    // 获取余票数量
    public int getCount() {
        return count;
    }

    // 售票
    public synchronized int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }
}

下面的这个由于涉及到两个对象,因此需要锁住该类

@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) + 1;
    }
}

// 账户
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    // 转账
    public void transfer(Account target, int amount) {
        synchronized(Account.class) {
            if (this.money >= amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}

对象头的相关信息#

image

image

intege占用空间分析,一个int占用4个字节,然后一个包装对象需要包含对象头核对象值,对象头需要占用8个字节,int中一个包装类型是普通类型的4倍

image


obj中的对象头里记录着对象的锁的地址

image


后面synchronized的原理感觉很复杂,后面再看

image

image

wait 和notify#

    @Test
    public  void test() throws InterruptedException {
        Object lock  = new Object();
        new Thread(()->{
            synchronized (lock) {
                log.info("我睡眠了");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("我被唤醒了");
            }
        },"t1").start();
        new Thread(()->{
            synchronized (lock) {
                log.info("我睡眠了");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("我被唤醒了");
            }
        },"t2").start();
        TimeUnit.SECONDS.sleep(2);
        synchronized (lock){
            log.info("我随机唤醒一个");
            lock.notify();
        }
    }

wait和notify的正确姿势

image

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
				// 用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了
                while (!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);
                while (!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.notifyAll();
            }
        }, "送外卖的").start();


    }

保护性暂停#

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
  • JDK 中,join 的实现、Future 的实现,采用的就是此模式
  • 因为要等待另一方的结果,因此归类到同步模式

    image

主线程等待另外一个线程获取结果

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        GuardObject guardObject = new GuardObject();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("获取到了结果");
            guardObject.complete("hello");
        }).start();
        log.info("主线程等待结果");
        log.info(""+guardObject.get());
    }

}
class GuardObject{
    private Object response;
    private final Object lock = new Object();
    public Object get() throws InterruptedException {
        synchronized (lock) {
            while (Objects.isNull(response)){
                lock.wait();
            }
            return response;
        }
    }
    public void complete(Object response) {
        synchronized (lock) {
            this.response = response;
            lock.notifyAll();
        }
    }
}

join底层就是应用该线程进行操作
带超时时间的等待

image


多任务的

image

相关事例

@Data
@Slf4j
public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }
        TimeUnit.SECONDS.sleep(2);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "内容" + id).start();
        }
    }

}
@Slf4j
class People extends Thread{
    @Override
    public void run() {
        GuardedObject guardedObject = Mailboxes.createGuardedObject();
        log.info("开始收信 id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        log.info("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
    }
}
@Slf4j
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.info("送信 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) {
        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 (Objects.isNull(response)) {
                // 这一轮循环应该等待的时间
                long waitTime = timeout - passedTime;
                // 经历的时间超过了最大等待时间时,退出循环
                if (waitTime <= 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();
        }
    }
}

以上是生产者和消费者一一对应,生产的消息回立即被消费

生产者和消费者#

image

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        MessageQueue messageQueue = new MessageQueue(2);
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info( messageQueue.take().toString());
            },"消费者"+i).start();
        }
        for (int i = 0; i < 5; i++) {
            int id = i;
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("防止的id为"+id);
                messageQueue.put(new Message(id,"消息"+id));
            },"生产者"+i).start();
        }

    }

}
@Data
class Message {
    private int id;
    private Object message;

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

    public int getId() {
        return id;
    }

    public Object getMessage() {
        return message;
    }
}
@Slf4j
class MessageQueue {
    private LinkedList<Message> queue;
    private int capacity;
    public MessageQueue(int capacity) {
        this.capacity = capacity;
        queue = new LinkedList<>();
    }
    public Message take() {
       synchronized (queue) {
           while (queue.isEmpty()){
               log.info("队列为空,等待...");
               try {
                   queue.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           queue.notifyAll();
          return queue.removeFirst();
       }
    }
    public void put(Message message) {
        synchronized (queue) {
            while (queue.size()==capacity){
                log.info("队列满了,等待...");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(message);
            queue.notifyAll();
        }
    }
}

也可以消费者只有一个,一直循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值