图文|Android 使用Thread 和多线程使用互斥锁

640?wx_fmt=jpeg

640?wx_fmt=jpeg

640?wx_fmt=png

为什么需要多线程进行开发?

多线程不管是嵌入式系统RTOS,Linux,还是应用开发,中间件开发,都是必不可少的,做一个技术的时候,如果能做到举一反三,下次使用的时候不会再遇到坑,我这次给出的例子是Android 的多线程开发。

640?wx_fmt=png

640?wx_fmt=png

如何使用一个线程

在Android 应用程序里面如何使用一个线程呢?直接看下面的代码,代码不是很多,如果需要用的话直接摘抄过去就好了。

//定义一个线程
private SendThread mSendThread = null;

/**
 * 线程实体
 */

private class SendThread extends Thread{

    public void run() {
    }
}

//实例化线程
if (mSendThread == null){
   mSendThread = new SendThread();
}

//启动线程
mSendThread.start();
640?wx_fmt=png

多次调用start是同一个堆栈空间吗?

如果只new了一次线程,多次start,会出现怎么样的情况呢?

android 线程start的函数原型如下

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */

        // Android-changed: throw if 'started' is true
        if (threadStatus != 0 || started)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */

        group.add(this);

        started = false;
        try {
            nativeCreate(this, stackSize, daemon);
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */

            }
        }
        }

然后我做了下面的一个代码实验

/**
 * 发送线程实体
 */

private class SendThread extends Thread{
    int testCount = 20;

    public void run() {
        while(testCount >0)
        {
            Log.d(TAG,"testCount:"+testCount);
            testCount --;
        }
    }
}

//实例化线程
if (mSendThread == null){
   mSendThread = new SendThread();
}

//启动线程
mSendThread.start();
mSendThread.start();

结果输出如下

D/ttyusb: testCount:20
D/ttyusb: testCount:20
D/ttyusb: testCount:19
D/ttyusb: testCount:19
D/ttyusb: testCount:18
D/ttyusb: testCount:18
D/ttyusb: testCount:17
D/ttyusb: testCount:16
D/ttyusb: testCount:17
D/ttyusb: testCount:15
D/ttyusb: testCount:16
D/ttyusb: testCount:14
D/ttyusb: testCount:15
D/ttyusb: testCount:13
D/ttyusb: testCount:14
D/ttyusb: testCount:12
D/ttyusb: testCount:13
D/ttyusb: testCount:11
D/ttyusb: testCount:12
D/ttyusb: testCount:10
D/ttyusb: testCount:9
D/ttyusb: testCount:11
D/ttyusb: testCount:8
D/ttyusb: testCount:10
D/ttyusb: testCount:7
D/ttyusb: testCount:9
D/ttyusb: testCount:6
D/ttyusb: testCount:5
D/ttyusb: testCount:8
D/ttyusb: testCount:4
D/ttyusb: testCount:7
D/ttyusb: testCount:3
D/ttyusb: testCount:6
D/ttyusb: testCount:2
D/ttyusb: testCount:1
D/ttyusb: testCount:5
D/ttyusb: testCount:4
D/ttyusb: testCount:3
D/ttyusb: testCount:2
D/ttyusb: testCount:1

可以看出线程每次start后,他们使用的堆栈空间是不相同的。

640?wx_fmt=png

在双线程里面使用互斥锁

使用互斥锁的情况非常普遍,但是新手写代码肯定会有道意想不到的问题,我就是那个新手,我就遇到了那个意想不到的问题。

640?wx_fmt=png

给出下面一段代码

/**
     * 线程实体
     */

    public class SendThread extends Thread {


        public void run() {
            isStart = true;
            for(int i=0;i<20;i++)
            {
                lock.lock();
                successCount = i;
                lock.unlock();
                Log.d(TAG, "Write:testCount:" + successCount);
            }
            isStart = false;
        }
    }

    /**
     * 接收数据的线程
     */

    public class ReceiveThread extends Thread {

        @Override
        public void run() {

            int testCount = 20;

            super.run();
            //条件判断,只要条件为true,则一直执行这个线程
            while (isStart == true) {

                testCount = successCount;

                Log.d(TAG, "Read:testCount:" + testCount);
            }

        }
    }

代码执行的流程大概如下

640?wx_fmt=png
640?wx_fmt=png

代码输出


03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:0
03-08 11:41:35.383 14866-14866/? D/TEST: 启动线程完成
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:1
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:2
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:1
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:3
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:3
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:4
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:4
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:5
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:5
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:6
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:6
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:7
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:7
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:8
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:8
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:9
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:9
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:10
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:10
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:11
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:11
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:12
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:12
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:13
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:13
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:14
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:14
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:15
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:15
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:16
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:16
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:17
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:17
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:18
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:18
03-08 11:41:35.383 14866-14906/? D/TEST: Write:testCount:19
03-08 11:41:35.383 14866-14907/? D/TEST: Read:testCount:19

上述代码问题

我们希望的代码流程是,我发送一个数据,你就接收一个数据,不能出现数据丢失的情况,但是上面的日子来看,接收数据是发生了丢失,这样的情况如果在实际应用中是非常危险的。

640?wx_fmt=png

所以代码需要类似下面这样修改

    /**
     * 线程实体
     */

    public class SendThread extends Thread {


        public void run() {
            isStart = true;
            for(int i=0;i<20;i++)
            {
                Log.d(TAG,"开始写线程~");
                lock.lock();
                successCount = i;
                Log.d(TAG, "写数据:testCount:" + successCount);
                lock.unlock();
                Log.d(TAG,"写线程休眠~");
                sendTreadSleep(10);
            }
            isStart = false;
        }
    }

    /**
     * 接收数据的线程
     */

    public class ReceiveThread extends Thread {

        @Override
        public void run() 
{

            int testCount = 20;

            super.run();
            //条件判断,只要条件为true,则一直执行这个线程
            while (isStart == true) {
                Log.d(TAG,"开始读线程~");
                lock.lock();
                testCount = successCount;
                Log.d(TAG, "读数据~:testCount:" + testCount);
                lock.unlock();
                Log.d(TAG,"读线程休眠~");
                receiveTreadSleep(5);
            }

        }
    }

工程的Demo到时候在文末给出,写线程在写完后,就休眠10MS,然后再到读线程执行,加的日志非常方便大家观看两个线程之间的运行逻辑。

640?wx_fmt=png

日志输出如下

 D/TEST: 启动线程完成
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:0
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:0
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:0
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:1
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:1
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:1
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:2
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:2
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:2
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:3
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:3
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 开始读线程~
 D/TEST: 写数据:testCount:4
 D/TEST: 写线程休眠~
 D/TEST: 读数据~:testCount:4
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:4
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:5
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:5
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:5
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:6
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:6
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:6
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:7
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:7
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:7
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:8
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:8
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:8
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:9
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:9
 D/TEST: 读线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:9
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:10
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:10
 D/TEST: 读线程休眠~
grande.ttyusb.test0001/com.evergrande.ttyusb.test0001.MainActivity] connect: already connected (cur=1 req=1)
 D/TEST: 开始读线程~
 D/TEST: 开始写线程~
 D/TEST: 读数据~:testCount:10
 D/TEST: 读线程休眠~
 D/TEST: 写数据:testCount:11
 D/TEST: 写线程休眠~
 D/mali_winsys: EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:11
 D/TEST: 读线程休眠~
 D/TEST: 开始写线程~
 D/TEST: 写数据:testCount:12
 D/TEST: 写线程休眠~
 D/TEST: 开始读线程~
 D/TEST: 读数据~:testCount:12

使用synchronized来达到完成互斥

synchronized修饰的方法可以让两个方法之间完成互斥,比如写和读互斥,写和写互斥,读和读互斥,都可以用这个方法。
使用代码如下

  /**
     * 线程实体
     */

    public class SendThread extends Thread {


        public void run() {
            isStart = true;
            for(int i=0;i<10;i++)
            {
                Log.d(TAG,"开始写线程~");
                mDataValue.setData(i);
                Log.d(TAG, "写数据:testCount:" + successCount);
                Log.d(TAG,"写线程休眠~");
                sendTreadSleep(10);
            }
            isStart = false;
        }
    }

    /**
     * 接收数据的线程
     */

    public class ReceiveThread extends Thread {

        @Override
        public void run() {

            int testCount = 20;
            Log.d(TAG,"s开始读线程~");
            super.run();
            //条件判断,只要条件为true,则一直执行这个线程
            while (isStart == true) {
                Log.d(TAG,"开始读线程~");
                testCount = mDataValue.getData();
                Log.d(TAG, "读数据~:testCount:" + testCount);
                Log.d(TAG,"读线程休眠~");
                receiveTreadSleep(5);
            }
        }
    }

    /**
     * 发送线程延迟
     * @param millis 毫秒
     */

    private void sendTreadSleep(int millis)
    
{
        try{
            mSendThread.sleep(millis);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 接收线程延迟
     * @param millis 毫秒
     */

    private void receiveTreadSleep(int millis)
    
{
        try{
            mReceiveThread.sleep(millis);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class DataValue{

        private synchronized void setData(int value){
            Log.d(TAG,"设置数据~setData");
            successCount = value;
        }

        private synchronized  int getData(){
            Log.d(TAG,"获取数据~getData");
            return successCount;
        }

    }

读写锁ReentrantReadWriteLock

上面是使用互斥锁,这里介绍一个读写锁,也是用来完成互斥的。使用代码如下

    /**
     * 线程实体
     */

    public class SendThread extends Thread {


        public void run() {
            isStart = true;
            for(int i=0;i<10;i++)
            {
                mrwDataValue.setData(i);
                Log.d(TAG,Thread.currentThread().getName() +"写休眠");
                sendTreadSleep(10);
            }
            isStart = false;
        }
    }

    /**
     * 接收数据的线程
     */

    public class ReceiveThread extends Thread {

        @Override
        public void run() 
{

            int testCount = 20;
            Log.d(TAG,"s开始读线程~");
            super.run();
            //条件判断,只要条件为true,则一直执行这个线程
            while (isStart == true) {
                mrwDataValue.getData();
                Log.d(TAG,Thread.currentThread().getName() +"读休眠");
                receiveTreadSleep(5);
            }
        }
    }
    /*
     * 使用读写锁完成互斥ReadWriteLock
     */

    private class rwDataValue{
        private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private int Data;

        private void setData(int value){
            readWriteLock.writeLock().lock();
            try {
                Log.d(TAG, Thread.currentThread().getName() +"写数据~setData:"+value);
                Data = value;
                Thread.sleep(30);
            }catch (Exception i){
                Log.e(TAG,"error");
            }finally {
                readWriteLock.writeLock().unlock();
            }
        }

        private  void getData(){
            readWriteLock.readLock().lock();
            try {
                Log.d(TAG,Thread.currentThread().getName() +"获取数据~getData:" +Data);
                Thread.sleep(10);
            }catch (Exception i){
                Log.e(TAG,"error");
            }finally {
                readWriteLock.readLock().unlock();
            }
        }
640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

640?wx_fmt=png

参考

https://blog.csdn.net/zy_style/article/details/53423877

Demo 代码

https://github.com/weiqifa0/androitdThread


640?wx_fmt=png


当你看到这里的时候,说明你已经阅读完上面的内容

不管怎样,感谢您有心或者无意的关注和支持

想获取学习1024G资料,请点击状态栏公众号福利按钮


640?wx_fmt=jpeg




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

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

相关文章

计算机专业的学生该选择日后的人生道路?继续从事IT还是考公务员……

问题来自知乎原问题如下&#xff1a;知乎上的盆友们大家好&#xff01;我是某高校大二学生。万能的知友们&#xff0c;有相关经验的大家给点宝贵意见呗&#xff0c;万分感谢。个人问题&#xff1a;我当时是听说这一行工资高&#xff0c;因为家里条件一般&#xff0c;所以想要挣…

汇编比较两个数大小_计算机是怎样跑起来的 -- 体验一次汇编过程

标志内存或I/O中存储单元的数字叫做“地址”。CPU中的标志寄存器有什么作用&#xff1f;用于在运算指令执行后&#xff0c;存储运算结果的某些状态。从程序员的角度看硬件CPU&#xff1a;种类&#xff0c;时钟信号的频率。可以使用哪种机器语言取决于CPU的种类。内存信息&#…

android区块链 钱包_区块链钱包Cashbox 开发工程师聊一聊开源

今天与大家聊一聊软件开源。事情的起因是一次闲谈的时候&#xff0c;市场部的同事表示&#xff1a;不做软件开发的人不能够理解为什么要把软件源代码公开出去。当时的我很惊讶&#xff0c;因为就像他们不理解为什么要开源一样&#xff0c;我也不理解他们为什么不理解为什么要开…

最新车载导航端口检测工具_高德地图这个功能 把微信都没做好的车载社交解决了?...

在汽车网联化和智能化带给人们的诸多想象里&#xff0c;车内社交似乎是最虚无缥缈的那个。在移动互联网领域&#xff0c;微信通过满足人们社交需求这个基本点&#xff0c;构建起庞大的用户群体&#xff0c;展示出巨大的市场前景。但在驾车场景下&#xff0c;车载社交该怎么玩&a…

王译潇20162314 第九周作业总结

学号 20162314 2016-2017-2 《Java程序设计》第九周学习总结 教材学习内容总结 数据库是为其他程序提供数据的应用软件 关系数据库通过唯一的标识符在不同表的记录间建立了关系 JDBC API用来建立到数据库的连接 CREATE TABLE SQL 语句用来创建新的数据库表 ALTER TABLE SQL 语句…

sketchup边线设置_春天花花天桥,SketchUp草图大师快速建模!

最近小吧在网上看到一座设计很特别的天桥&#xff0c;形状很像一朵花&#xff0c;名字也十分写实——春花天桥。喏&#xff0c;就是下面这个家伙&#xff01;图片来自网络春花天桥是2011年深圳举办大运会前夕投资建设的形象提升工程之一&#xff0c;在建设初期就被定位为地标性…

android 7.1 apk的systemuid和系统应用Setting相同导致开机找不到库的问题

1、前言Android apk加载机制这就是我最近在解决的一个问题&#xff0c;32位的apk可以用32位的库&#xff0c;那64位的apk可以用64位的库&#xff0c;如果我想64位的apk同时使用32和64位的库呢&#xff1f;android加载so文件的机制apk在安装的过程中&#xff0c;系统就会对apk进…

怎样的人适合当码农?

1高中的时候&#xff0c;我一个友仔&#xff0c;他说要创办一个科技协会&#xff0c;然后跟我说电脑里面的代码都是0和1组成的&#xff0c;我现在还记得那天我们在学校开会的时候讨论电脑如何运行&#xff0c;我一脸痴呆的听他给我讲解电子知识&#xff0c;而且他后面自己做了一…

android 7.1 apk的systemuid [2]

1 上周发的一篇文章《android 7.1 apk的systemuid和系统应用Setting相同导致开机找不到库的问题》&#xff0c;然后有个小伙伴找我讨论了&#xff0c;觉得自己的脑洞了打开了很多&#xff0c;所以针对这个问题再次做一次总结&#xff0c;如果大家有好的建议也可以在文末留言。…

Struts2_3_国际化处理_自定义拦截器_文件上传及下载_OGNL

Struts2国际化处理 浏览器根据当前的语言环境自动查找对应的语言环境资源包, 使jsp显示合适的语言数据环境 Struts2实现国际化, 动作类必须继承ActionSupport 创建资源包 资源包由多个properties文件组成, properties文件的文件名必须满足命名规范: 文件名_语言代码_国家代码…

RBAC 基于角色的访问控制

RBAC&#xff08;Role-Based Access Control&#xff0c;基于角色的访问控制&#xff09;&#xff0c;就是用户通过角色与权限进行关联。简单地说&#xff0c;一个用户拥有若干角色&#xff0c;每一个角色拥有若干权限。这样&#xff0c;就构造成“用户-角色-权限”的授权模型。…

C语言书籍大全

学习过linux的同学应该都知道&#xff0c;linux系统几乎都是用c编写&#xff0c;包括现在主流的android,ios系统&#xff0c;都是使用c编程。为了方便大家学习C语言&#xff0c;小编搜集整理了市面几乎所有的C语言书籍&#xff0c;下面给出部分比较有名的&#xff0c;在微信公众…

android 7.1 apk的systemuid相同导致问题[2]

1上周发的一篇文章《android 7.1 apk的systemuid和系统应用Setting相同导致开机找不到库的问题》&#xff0c;然后有个小伙伴找我讨论了&#xff0c;觉得自己的脑洞了打开了很多&#xff0c;所以针对这个问题再次做一次总结&#xff0c;如果大家有好的建议也可以在文末留言。2先…

送书送书送书,(包邮)送,仅此而已!

亲爱的各位fans们&#xff0c;大家好&#xff0c;这次小编联合5个公众号朋友一起送书&#xff0c;一共送出 6本&#xff0c;当然包邮&#xff01;感谢本次活动的赞助商北京大学出版社。6个公众号联合送书&#xff0c;一共6本书&#xff0c;每个公众号送1本(包邮)。 6个公众号同…

requests 获取div_爬虫系列第五篇 使用requests与BeautifulSoup爬取豆瓣图书Top250

上一篇我们学习了BeautifulSoup的基本用法&#xff0c;本节我们使用它来爬取豆瓣图书Top250。一、网页分析我们爬取的网页的url是https://book.douban.com/top250?icnindex-book250-all。首页如图与豆瓣电影Top250差不多&#xff0c;将页面拉到最底部&#xff0c;可以看到分页…

python--socket套接字/TCP

socket套接字/TCP 一 客户端/服务器架构 C/S架构&#xff0c;包括 硬件C/S架构&#xff08;打印机&#xff09;软件C/S 架构&#xff08;web服务&#xff09;C/S架构的软件&#xff08;软件属于应用层&#xff09;是基于网络进行通信的Server端要&#xff1a; 1.力求一直提供服…

uniapp封装网络请求_八张图带你走进“通过一个完美请求封装一个网络模块”

本文提供视频课程讲解&#xff0c;需要的小伙伴可以点赞私信‘’网络模块‘’前往领取学习大纲1、网络模块在应用中的地位1.1当今占统治地位的网络组件OKHttp OkHttp 是一个相对成熟的解决方案&#xff0c;据说 Android4.4 的源码中可以看到 HttpURLConnection 已经替换成 OkHt…

毕业的这0111年

1.2004年&#xff0c;不知道大家对这个时间有没有感觉&#xff0c;那几年我正在读高中&#xff0c;韩寒的《三重门》席卷校园&#xff0c;同样还有郭敬明的《夏至未至》。那时候的我&#xff0c;还挣扎在温饱阶段&#xff0c;我每天吃饭的时候都想上食堂的三楼吃风味&#xff0…

我的互助小蜜圈

写在前面 从不久之前到现在&#xff0c;我的微信公众号从一个小小小阶段到了另一个小小阶段&#xff0c;非常感谢各位读者对我的信任和支持&#xff0c;当然我也是非常用心的维护这这群大佬&#xff0c;时刻想着怎样给各位大佬做好服务 &#xff0c;比如我正在溜娃&#xff0c…

比较两个表格的不同_两表数据的核对,WPS表格似乎更加方便容易

在EXCEL中的两个表格的数据对比&#xff0c;可能使用的方法会采用到查询语句&#xff0c;VBA之类的。显得在使用的过程显得有些复杂。在不经间发现WPS表格的功能处理此类的问题显得就比较的简单的多。查看并标识出分数相同的内容如下面的分数表&#xff0c;选择分数栏的范围&am…