Java中的生产者/消费者模型

news2024/11/19 12:33:48

一、什么是生产者/消费者模型

生产者-消费者模型(Producer-Consumer problem)是一个非常经典的多线程并发协作的模型。

比如某个模块负责生产数据,而另一个模块负责处理数据。产生数据的模块就形象地被称为生产者;而处理数据的模块,则被称为消费者。

生产者和消费者在同一段时间内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

如图所示:

 二、生产者-消费者模式的优点

1、解耦

由于有缓冲区的存在,生产者和消费者之间不直接依赖,耦合度降低。

2、支持并发

由于生产者与消费者是两个独立的并发体,它们之间是通过缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区中拿数据接口,这样就不会因为彼此的处理速度而发生阻塞。(通过使用多个生产者和消费者线程,可以实现并发处理,提高系统的吞吐量和响应性)

3、支持忙闲不均

缓冲区还有另一个好处:当数据生产过快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等消费者处理掉其他数据时,再从缓存区中取数据来处理。(通过使用缓冲区可以平衡生产者与消费者之间的速度差异,以及处理能力的不匹配)

三、生产者-消费者模式所遵循的规则

  • 生产者仅仅在缓冲区未满时生产,缓冲区满则停止生产。
  • 消费者仅仅在缓冲区有产品时才能消费,缓冲区为空则停止消费。
  • 当消费者发现缓冲区没有可消费的产品时会通知生产者。
  • 当生产者生产出可消费的产品时,应该通知等待的消费者去消费。

四、生产者-消费者模型的实现

1、通过阻塞队列方式实现

public class ProducerConsumerDemo1 {

    /**
     * 缓冲队列
     */
    private final ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(10);

    /**
     * 生产者
     */
    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                blockingQueue.offer(i);
            }
        }
    }

    /**
     * 消费者
     */
    class Consumer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                blockingQueue.poll();
            }
        }
    }

    public Producer getProducer() {
        return new Producer();
    }

    public Consumer getConsumer() {
        return new Consumer();
    }


    public static void main(String[] args) {
        ProducerConsumerDemo1 producerConsumerDemo1 = new ProducerConsumerDemo1();
        new Thread(producerConsumerDemo1.getProducer()).start();
        new Thread(producerConsumerDemo1.getConsumer()).start();
    }
}

2、通过wait和notifyAll来实现

    /**
     * 缓冲区(仓库)
     */
    private final List<Object> list = new ArrayList<>();

    private final int bufferCount = 10;

    public final Object lock = new Object();

    /**
     * 生产者
     */
    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock) {
                    while (list.size() >= bufferCount) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //仓库未满,继续生产产品
                    list.add(new Object());
                    //唤醒消费者去消费产品
                    lock.notifyAll();
                }
            }
        }
    }

    /**
     * 消费者
     */
    class Consumer implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (lock) {
                    while (list.size() == 0) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //仓库不是空,继续消费
                    list.remove(0);
                    //唤醒生产者去生产产品
                    lock.notifyAll();
                }
            }
        }
    }

    public Producer getProducer(){
        return new Producer();
    }

    public Consumer getConsumer(){
        return new Consumer();
    }

    public static void main(String[] args) {
        ProducerConsumerDemo2 producerConsumerDemo2=new ProducerConsumerDemo2();
        new Thread(producerConsumerDemo2.getProducer()).start();
        new Thread(producerConsumerDemo2.getConsumer()).start();
    }
}

3、通过ReentrantLock和Condition来实现

public class ProducerConsumerDemo3 {

    /**
     * 缓冲区(仓库)
     */
    private final List<Object> list = new ArrayList<>();
    /**
     * 缓冲区大小
     */
    private final int bufferCount = 10;
    public ReentrantLock lock = new ReentrantLock();
    //创建两个条件变量
    private final Condition condition1 = lock.newCondition();
    private final Condition condition2 = lock.newCondition();


    /**
     * 生产者
     */
    class Producer implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);//模拟生产操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                try {
                    lock.lock();
                    while (list.size() >= bufferCount) {
                        condition1.await();//当仓库数据数量超过缓冲区设定的最大数量,则让生产线程进入等待状态
                    }

                    list.add(new Object());
                    System.out.println(Thread.currentThread().getName() + "-生产者生产,数量为:" + list.size());
                    condition2.signal();//唤醒消费线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

            }
        }
    }

    class Consumer implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                try {
                    lock.lock();
                    while (list.size() == 0) {
                        condition2.await();//当仓库中数据为空时,则让消费线程进入等待状态
                    }
                    list.remove(0);
                    System.out.println(Thread.currentThread().getName() + "-消费者消费,数量为:" + list.size());
                    condition1.signal();//唤醒生产线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    public Producer getProducer() {
        return new Producer();
    }

    public Consumer getConsumer() {
        return new Consumer();
    }

    public static void main(String[] args) {
        ProducerConsumerDemo3 producerConsumerDemo3 = new ProducerConsumerDemo3();
        new Thread(producerConsumerDemo3.getProducer()).start();
        new Thread(producerConsumerDemo3.getConsumer()).start();
    }
}

五、使用两个线程轮流打印字符串A和字符串B(A和B交替打印,各打印10次。

1、通过wait()和notifyAll()实现:

public class PrintDemo2 {

    private int num = 10;
    private boolean flag;

    private final Object obj = new Object();

    public static void main(String[] args) {
        PrintDemo2 printDemo2 = new PrintDemo2();
        printDemo2.printMethod();
    }

    public void printMethod() {
        Thread thread1 = new Thread("Thread One") {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < num; i++) {
                    synchronized (obj) {
                        while (flag) {
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(Thread.currentThread().getName() + "=========A");
                        flag = true;
                        obj.notifyAll();
                    }
                }

            }
        };

        Thread thread2 = new Thread("Thread Two") {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < num; i++) {
                    synchronized (obj) {
                        while (!flag) {
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        System.out.println(Thread.currentThread().getName() + "=========B");
                        flag = false;
                        obj.notifyAll();
                    }
                }
            }
        };

        thread1.start();
        thread2.start();
    }

}

打印结果如下:

小结:关键字synchronized与wait()、notify()/notifyAll()方法相结合可以实现wait/notify模式,ReentrantLock同样可以实现同样的功能,但需要借助于Condition对象。  下面我们来看一下使用ReentrantLock+Condition来实现的方式。

2、方式二:使用ReentrantLock和Condition实现:

public class PrintDemo {

    private int num = 10;
    private boolean flag;

    public static void main(String[] args) {
        PrintDemo printDemo = new PrintDemo();
        printDemo.printMethod();
    }

    public void printMethod() {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        Thread thread1 = new Thread("Thread One") {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < num; i++) {
                    try {
                        lock.lock();
                        while (flag) {
                            condition.await();
                        }

                        System.out.println(Thread.currentThread().getName() + "=========A");
                        flag = true;
                        condition.signalAll();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        lock.unlock();
                    }
                }
            }
        };


        Thread thread2 = new Thread("Thread Two") {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < num; i++) {
                    try {
                        lock.lock();
                        while (!flag) {
                            condition.await();
                        }

                        System.out.println(Thread.currentThread().getName() + "=========B");
                        flag = false;
                        condition.signalAll();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            }
        };


        thread1.start();
        thread2.start();
    }

}

打印结果如下:


       

六、等待通知机制

是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

  • wait():表示让当前线程进入等待状态,并且释放对象上的锁及CPU资源。然后无限期等待,直到被唤醒为止(注意:调用wait()方法后会释放对象的锁)
  • nofity():通知一个正在等待的线程(是从等待中的线程中随机通知一个),使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。
  • notifyAll():与notify方法相同,唯一不同的是,notifyAll是通知所有等待的线程,而notify是随机地通知等待中的线程中的一个。
  • wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待n毫秒。线程将等待指定的时间长度后自动苏醒,或者直到其他线程通过调用相同对象上的notify()或者notifyAll()来唤醒它。如果超过了指定时间没有被唤醒,则线程会自动苏醒。
  • wait(long ,int):同上述wait(long),只是对于超时的时间更细粒度的控制,可以达到纳秒。

注意:在调用wait()、notify()、notifyAll()方法之前,线程必须要获得对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法、notify()、notifyAll()方法。

1、等待和通知的标准范式

等待方遵循如下原则:

(1)获取对象的锁

(2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件

(3)条件满足则执行对应的逻辑

 通知方遵循如下原则:

(1)获得对象的锁

(2)改变条件

(3)通知所有等待在对象上的线程。

2、notify和notifyAll应该用哪一个

尽可能用notifyAll(),谨慎用notify(),因为notify()只会唤醒一个线程,我们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程。

3、为什么wait方法必须在synchronized保护的同步代码块中使用?

先来看看wait方法的源码注释:

“wait method should always be used in a loop:

 synchronized (obj) {
     while (condition does not hold)
         obj.wait();
     ... // Perform action appropriate to condition
}

 This method should only be called by a thread tah is the owner of this object's monitors"

意思就是说,在使用wait方法时,必须把wait方法写在synchronized保护的while代码块中,并始终判断执行条件是否满足,如果满足就往下继续执行,如果不满足就执行wait方法,而在执行wait方法之前,必须先持有锁对象的monitor对象。

4、wait/notify 和 sleep方法的异同?

相同点:

(1)它们都可以让线程阻塞

(2)它们都可以响应interrupt中断:在执行的过程中,如果收到中断信号都可以响应,并抛出InterruptedException异常

不同点:

(1)wait方法必须在synchronized保护的代码中使用,而sleep方法并没有这个要求

(2)在同步代码中执行sleep方法时,并不会释放monitor锁,但执行wait方法时会主动释放monitor锁

(3)sleep方法中要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的wait方法而言,意味着永久等待,直到被中断或唤醒才能恢复,它并不会主动恢复。

(4)wait/notify 是Object 类的方法,而sleep是Thread类的方法。

5、Java多线程中wait()为什么要放在while循环中

将wait()方法放在while循环中是为了避免虚假唤醒(spurious wakeups)。

虚假唤醒是指当一个线程被唤醒时,尽管没有收到对应的通知信号,但它还是会继续执行。这种情况可能发生在某些特定的条件下,例如操作系统级别的中断、调度器的行为等。

为了防止虚假唤醒导致逻辑或数据不一致性问题,在使用wait()方法进行线程等待时,我们通常会将其放在一个while循环内,并且与需要满足的条件进行检查。只有当满足特定的条件才会继续执行后面的代码。如果不满足条件,则线程会再次进入等待状态。以下是一个示例:

synchronized (lock) {
    while (!condition) {
        lock.wait();
    }
    
    // 执行其他操作
}

通过使用while(condition)来检查条件是否满足,即使发生虚假唤醒也能够安全地重新检查和等待。因此,在多线程编程中建议将wait()方法置于while循环内部以保证正确性。

6、为什么wait/notify/notifyAll 被定义在Object类中,而sleep被定义在Thread类中?

wait(),notify()和notifyAll()方法被定义在Object类中而不是Thread类中的原因是因为它们操作的是对象的锁(monitor)。

这些方法用于实现线程之间的协作与通信机制,即等待其他线程满足特定条件后再继续执行。由于每个对象都有一个关联的锁,因此将这些方法定义在所有对象共享的父类Object中更加合适。通过调用这些方法来控制同步代码块或同步方法内部对锁资源进行管理。

另一方面,sleep()方法并定义在Thread类中,它使当前正在执行的线程暂停指定时间段,并且不会释放持有的任何锁。该方法属于线程级别的操作,在调用时直接影响当前运行状态下的线程自身。

总结起来:

wait(),notify()和notifyAll()是基于对象级别的操作,用于多个线程之间进行协作和通信。

由于每个Java对象都具备监视器(monitor)功能(即互斥锁),所以这些方法需要定义在所有Java对象共享的父类Object中。

而sleep()方法则是一个与当前正在运行状态下的Thread相关联,独立存在并影响其自身行为状态。

 7、ReentrantLock和Condition

Condition类是JDK5的技术,具有更好的灵活性,例如,可以实现多路通知功能,也就是在一个Lock对象中可以创建多个Condition实例,线程对象注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

        在使用notify()/notifyAll()方法进行通知时,被通知的线程由JVM进行选择,而方法notifyAll()会通知所有的waiting线程,没有选择权,会出现相当大的效率问题,但使用ReentrantLock结合Condition类可以实现”选择性通知“,这个功能是Condition类默认提供的。

       Condition对象的作用是控制并处理线程的状态,它可以使线程呈现wait状态,也可以使线程继续运行。

        Condition的await()方法的作用是使当前线程在接到通知或被中断之前一直处于等待wait状态。它和wait()方法的作用一样。

condition.await()方法调用之前必须先调用lock.lock()方法获得锁,否则会抛出java.lang.IIIleagalMonitorStateException:current thread is not owner。如下所示:

public class PrintDemo3 {

    public static void main(String[] args) {
        PrintDemo3 printDemo3=new PrintDemo3();
        printDemo3.printMethod();
    }

    public void printMethod() {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    System.out.println("MM");
                    condition.wait();
                    System.out.println("NN");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        thread1.start();
    }
}

控制台的输出:

  • Object类中的wait()方法相当于Condition类中的await()方法。
  • Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)方法。
  • Object类中的notify()方法相当于Condition类中的signal()方法。
  • Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/813259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基地培训一周总结-用两台虚拟机模拟公司和员工

前言&#xff1a; 本来是打算每天跟更新所学内容&#xff0c;但奈何自己接触新知识速度较慢&#xff0c;每天都在完不成任务的边缘疯狂试探&#xff0c;短时间大量知识的涌入&#xff0c;感觉脑袋瓜在有点 跟不上。这周结束的时候&#xff0c;老师布置了个小项目&#xff0c;融…

Linux操作系统下安装python环境

参考&#xff1a;Linux操作系统下安装python环境_linux如何下载python_秃头小猿-F的博客-CSDN博客 注意 切换用户 二、切换root用户 1.给root用户设置密码&#xff1a;命令&#xff1a;sudo passwd root输入密码&#xff0c;并确认密码。2.重新输入命令&#xff1a;su root …

【数据结构】之十分好用的“链表”赶紧学起来!(第一部分单向链表)

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

某行动态cookie反爬虫分析

某行动态cookie反爬虫分析 1. 预览 反爬网址(base64): aHR0cDovL3d3dy5wYmMuZ292LmNu 反爬截图&#xff1a; 需要先加载运行js代码&#xff0c;可能是对环境进行检测&#xff0c;反调试之类的 无限debugger 处理办法 网上大部分人说的都是添加cookie来解决。 那个noscrip…

哈工大计算机网络课程网络安全基本原理之:身份认证

哈工大计算机网络课程网络安全基本原理之&#xff1a;身份认证 在日常生活中&#xff0c;在很多场景下我们都需要对当前身份做认证&#xff0c;比如使用密码、人脸识别、指纹识别等&#xff0c;这些都是身份认证的常用方式。本节介绍的身份认证&#xff0c;是在计算机网络安全…

flask处理表单数据

flask处理表单数据 处理表单数据在任何 web 应用开发中都是一个常见的需求。在 Flask 中&#xff0c;你可以使用 request 对象来获取通过 HTTP 请求发送的数据。对于 POST 请求&#xff0c;可以通过 request.form 访问表单数据。例如&#xff1a; from flask import Flask, r…

设置Fiddler来抓取Android接口数据

1.下载安装fiddler&#xff0c;安装包可自行百度。安装完成打开fiddler 2.将Fiddler设置远程访问PC 选择Fiddler->Tools->Fiddler Option 3.选择Connection&#xff0c;在Fiddler listen on port后输入8888&#xff0c;表示允许远程PC连接。 4.在电脑运行窗口中&#xf…

Leetcode145. 二叉树的后序遍历

题目描述 题目链接&#xff1a;https://leetcode.cn/problems/binary-tree-postorder-traversal/description/ 代码实现 class Solution {List<Integer> tree new ArrayList<>();public List<Integer> postorderTraversal(TreeNode root) {postorder(ro…

深度学习实践——卷积神经网络实践:裂缝识别

深度学习实践——卷积神经网络实践&#xff1a;裂缝识别 系列实验 深度学习实践——卷积神经网络实践&#xff1a;裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 深度学习实践——卷积神经网络实践&#xff…

Android SDK 上手指南||第一章 环境需求||第二章 IDE:Eclipse速览

第一章 环境需求 这是我们系列教程的第一篇&#xff0c;让我们来安装Android的开发环境并且把Android SDK运行起来&#xff01; 介绍 欢迎来到Android SDK入门指南系列文章&#xff0c;如果你想开始开发Android App&#xff0c;这个系列将从头开始教你所须的技能。我们假定你…

NOSQL之Redis配置及优化

目录 一、关系型数据库 二、非关系型数据库 三、关系型数据库和非关系型数据库区别 1、数据存储方式不同 2、扩展方式不同 3、对事务性的支持不同 四、Redis简介 五、Redis优点 &#xff08;1&#xff09;具有极高的数据读写速度 &#xff08;2&#xff09;支持丰富的…

软件架构师——1、计算机组成与体系结构

计算机结构 &#xff08;★&#xff09; 运算器&#xff1a; 算术逻辑单元ALU&#xff1a;数据的算术运算和逻辑运算累加寄存器AC&#xff1a;通用寄存器&#xff0c;为ALU提供一个工作区&#xff0c;用于暂存数据数据缓冲寄存器DR&#xff1a;写内存时&#xff0c;暂存指令或…

Nginx 如何根据swagger关键字屏蔽页面

目录 一、知识回顾1.什么是 location2.location 语法3.location 的匹配规则4.优先级排序 二、如何根据关键字筛选请求1.实现方案2.测试结果 一、知识回顾 1.什么是 location Nginx 中通过根据 location 块的规则来将匹配到的 URL 请求进行一系列操作&#xff0c;最常见的就是…

【C++】STL——stack的介绍和使用、stack的push和pop函数介绍和使用、stack的其他成员函数

文章目录 1.stack的介绍2.stack的使用2.1stack构造函数2.1stack成员函数&#xff08;1&#xff09;empty() 检测stack是否为空&#xff08;2&#xff09;size() 返回stack中元素的个数&#xff08;3&#xff09;top() 返回栈顶元素的引用&#xff08;4&#xff09;push() 将元素…

QT数据库编程

ui界面 mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QButtonGroup> #include <QFileDialog> #include <QMessageBox> MainWindow::MainWindow(QWidget* parent): QMainWindow(parent), ui(new Ui::M…

递归算法判断是否是“平衡二叉树”

题目&#xff1a; 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;true 解…

【Unity2D】粒子特效

为什么要使用粒子特效 Unity 的粒子系统是一种非常强大的工具&#xff0c;可以用来创建各种各样的游戏特效&#xff0c;如火焰、烟雾、水流、爆炸等等。使用粒子特效可以使一些游戏动画更加真实或者使游戏效果更加丰富。 粒子特效的使用 在Hierarchy界面右键添加Effects->…

ICASSP 2023 | Cough Detection Using Millimeter-Wave FMCW Radar

原文链接&#xff1a;https://mp.weixin.qq.com/s?__bizMzg4MjgxMjgyMg&mid2247486540&idx1&sn6ebd9f58e9f08a369904f9c48e12d136&chksmcf51beb5f82637a3c65cf6fa53e8aa136021e35f63a58fdd7154fc486a285ecde8b8521fa499#rd ICASSP 2023 | Cough Detection Usi…

【Golang 接口自动化01】使用标准库net/http发送Get请求

目录 发送Get请求 响应信息 拓展 资料获取方法 发送Get请求 使用Golang发送get请求很容易&#xff0c;我们还是使用http://httpbin.org作为服务端来进行演示。 package mainimport ("bytes""fmt""log""net/http""net/url&qu…