Java之多线程初阶2

news2024/11/20 3:27:21

一.上节内容复习

上节内容指路:Java之多线程初阶

1.进程和线程的区别

1.进程中至少包含一个线程(主线程)

2.进程是申请计算机资源的最小单位

3.线程是CPU调度的最小单位

4.进程之间是相互隔离的,线程是使用的是进程统一申请来的资源,之间可以相互影响

2.创建线程的四种方式

1.继承Thread类并重写run()方法

2.实现Runnable接口并重写run()方法

3.通过匿名内部类创建Thread和实现Runnable

4.通过Lambda表达式实现一个线程

二.多线程的优点的代码展示

1.多线程的优点

通过上一节的学习我们知道多线程可以充分利用CPU的资源,提高程序的运行效率

2.代码实现

public class Demo6_10B {
    public static void main(String[] args) {
        serial();
        concurrent();

    }

    public static long COUNT = 10_0000_0000L;

    //串行执行10亿次的自增
    public static void serial() {
        long start = System.currentTimeMillis();
        long a = 0L;
        for (int i = 0; i < COUNT; i++) {
            a++;
        }
        long b = 0L;
        for (int i = 0; i < COUNT; i++) {
            b++;
        }
        long end = System.currentTimeMillis();

        System.out.println("串行的总耗时为:" + (end - start) + " ms");

    }

    //并行执行10亿次的自增
    public static void concurrent() {
        long start = System.currentTimeMillis();

        Thread thread1=new Thread(()->{
            long a=0L;
            for (int i = 0; i < COUNT; i++) {
                a++;
            }
        });
        Thread thread2=new Thread(()->{
            long a=0L;
            for (int i = 0; i < COUNT; i++) {
                a++;
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("并行的总耗时为:" + (end - start) + " ms");
        
    }
}

注意:并行代码进行实现的时候一定要加 thread1.join();和 thread2.join();这两段代码,join()方法的作用是等待线程执行完毕之后,继续执行下面的代码.如果我们没有这两段代码的话,可能会发生如下情况:两个线程的自增操作还没有完成,主线程(main)线程已经执行完毕,也就是直接打印最终执行的总耗时,正确的耗时的结果应该为串行耗时的一半多.

这里没有加join()代码执行的结果,可以看到主线程执行完毕,线程1和线程2还没有执行完毕.

加join()打印结果:

 我们多执行几次代码,发现每一次并行的耗时都是串行耗时的一半多一些,那么为什么不是一半呢?其实仔细思考我们不难想出:线程的创建和销毁也是需要一定时间的,因此总是一半多一些.

接下来我们将COUNT的值改小点,比如改到COUNT=10_000L,这个时候我们再执行

发现串行的总耗时比并行的总耗时还要短,因此我们可以大胆推测,并不是所有的场景下多线程的效率都是最高的,当我们的运算量很小的时候,创建线程的时间比代码运行的时间还短,这样子显然是不适合用多线程的.

三.Thread类常用的方法

1.Thread类中的构造方法

方法说明
Thread()创建一个线程对象
Thread(String name)创建一个线程对象,并为它命名
Thread(Runnable target)使用Runnable对象创建线程
Thread(Runnable target,String name)使用Runnable对象创建线程,并命名
【了解】 Thread(ThreadGroup group,
Runnable target)
线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可

Thread t1 = new Thread();

Thread t2 = new Thread ( new MyRunnable ());
Thread t3 = new Thread ( " 这是线程名 " );
Thread t4 = new Thread ( new MyRunnable (), "这是线程名" );

2.Thread类中的属性

属性
获取方法
ID
getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

1.为线程命名并获取线程的名字

public class Demo7_ThreadName {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println(Thread.currentThread().getName() + ":hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "这是thread线程");
        thread.start();

    }
}

 在jconsole中查看线程的名字

 当我们不执行线程名字的时候,系统会自动生成线程名,从Thread0开始依次向后进行生成

2.演示isDaemon()

我们之前使用的线程都是前台线程,因为创建线程之后默认都是前台线程,必须手动设置成为后台线程,我们通过        thread.setDaemon(true);代码将这个进行设置为后台进程

public class Demo8_Daemon {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println(Thread.currentThread().getName() + ":hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //手动设置为后台线程,默认为false,也就是前台线程
        thread.setDaemon(true);
        System.out.println("是否存活:" + thread.isAlive());
        // 启动线程
        thread.start();
        // 休眠一会,确保系统PCB创建成功
        Thread.sleep(500);
        System.out.println("是否存活:" + thread.isAlive());
        System.out.println("main线程执行完成");
        System.out.println("是否存活:" + thread.isAlive());
        
    }
}

打印结果如下:

 我们可以看出来,随着main方法的执行完毕,程序也进行了关闭

和前台进程进行对比,前台进程打印的结果如下:

 因此我们可以总结出前台进程和后台进程运行的场景

前台进程:在一些需要需要精确业务,容错率低的事务中采用前台进程,如银行转账,要确保转账线程的完成.

后台进程:在一些容错率高的任务,可以采用,如微信的步数计算,并不需要精确计算你精确的步数,不影响主线程的关闭..

3.演示isAlive()

public class Demo9_IsAlive {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            int cnt = 0;
            while (true) {
                System.out.println("hello thread.....");
                cnt++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (cnt == 5)
                    break;
            }
        });

        System.out.println("是否存活:" + thread.isAlive());
        // 启动线程
        thread.start();

        System.out.println("是否存活:" + thread.isAlive());
        //等待线程执行完毕
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("是否存活:" + thread.isAlive());

    }
}

打印的结果如下:

由此我们可以总结得出:isAlive()方法是判断系统线程(PCB)是否存活,并不是我们new出来的Thread对象

4.演示getState()

  • NEW:创建一个Java线程(对象),但还没有调用start()方法,也就是没有参与CPU调度,此时就是一个Java的对象
  • RUNNABLE: 运行或在就绪队列中(PCB的就绪队列)
  • BLOCKED: 等待锁的时候进入堵塞状态(synchronized)
  • WAITING: 没有时间限制的等待
  • TIMED_WAITING: 等待一段时间(有时间限制的等待)(过时不候)
  • TERMINATED:线程执行完成,PCB在操作系统中已经销毁,但是JAVA对象还在

线程状态之间的转换图

 

 这里来观察三种状态NEW-->TIME_WAITING-->TERMINATED

public class Demo14_State {
    public static void main(String[] args) {
        Thread thread=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println("启动之前的状态:"+thread.getState());

        thread.start();
        //等待线程的创建
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("启动之后的状态:"+thread.getState());
        //等待线程结束
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程完成之后的状态:"+thread.getState());

    }
}

打印的结果为: 

  这里来观察三种状态NEW-->RUNNABLE-->TERMINATED

public class Demo14_State {
    public static void main(String[] args) {
        Thread thread=new Thread(()->{
            for(long i=0;i<10000000000L;++i){

            }
        });
        System.out.println("启动之前的状态:"+thread.getState());

        thread.start();
        //等待线程的创建
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("启动之后的状态:"+thread.getState());
        //等待线程结束
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程完成之后的状态:"+thread.getState());

    }
}

打印的结果如下:

 

具体堵塞的状态BLOCK(synchronized具体的用法下一节)

public class Demo16_State2 {

    public static void main(String[] args) {
        final Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //此时object对象就是锁
                synchronized (object) {
                    while (true) {
                        System.out.println("张三");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("李四");
                    System.out.println("hehe");
                }
            }
        }, "t2");
        t2.start();
        //等待线程的创建
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程1此时的状态:"+t1.getState());
        System.out.println("线程2此时的状态:"+t2.getState());

    }

}

因为线程一的状态处以sleep(1000),所以他现在是TIMED_WAITING,因为线程一拿到了锁是object对象,而线程二也没有拿到,需要等到线程一释放锁才可以继续进行,所以线程二的状态为BLOCK

将上面代码的Thread.sleep(1000)代码改换为object.wait();代码可以观察到WAIT状态

具体的打印如下,可以看到线程一处在WAIT状态,每一继续向下打印,并且锁也没有释放,所以线程二一致处在BLOCK状态

结论:

1.BLOCKED 表示等待获取锁, WAITING TIMED_WAITING 表示等待其他线程发来通知.
2.TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒

3.Thread类中的方法

1.启动一个线程---start()方法

我们前面一节讲过start方法,通过调用start0()本地方法来启动一个线程

有上面的基础,这里我们来分析一下start()方法和run()方法的区别

1.start()方法是真正申请一个系统线程,run()方法是定义线程要执行的任务

2.直接调用run()方法,不会去申请一个真正的系统线程(PCB),而是调用对象的方法

调用start()方法,JVM会调用本地方法去申请一个真正的系统线程(PCB),并执行run()方法中的逻辑

接下来通过下段代码来更好的理解:

public class Demo13_StartRun {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("hello thread...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        });
        thread.run();
//        thread.start();
        System.out.println("线程状态:" + thread.getState());
        System.out.println("主线程结束");

    }
}

调用run()方法打印的结果:

调用start()方法打印的结果:

可以观察到调用run()方法根本不会打印主线程结束,而调用start()方法会打印.

2.等待一个线程---join()方法

join()方法的作用:等待当前线程结束,进行到下一步的工作

方法说明
public void join()
等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度
public class Demo12_Join {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; ++i) {
                System.out.println("hello,thread " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();

        System.out.println("join之前,线程状态:" + thread.getState());
        System.out.println("join之前,是否存活:" + thread.isAlive());
        // 使用join等待thread线程结束
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join之后,线程状态:" + thread.getState());
        System.out.println("join之后,是否存活:" + thread.isAlive());
        System.out.println("主线程执行完成。");


    }
}

打印的结果:

3.中断一个线程----interrupt()方法

线程的中断:停止或者中断当前线程的任务.

实现线程的中断有如下两种方式

1.通过是否中断的标志位(手动)

通过isQuit的修改来中断线程

public class Demo10_Interrupted {
    public static boolean isQuit = false;

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!isQuit) {
                System.out.println("hello thread...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            System.out.println("线程任务完成");
        });

        // 启动线程
        thread.start();
        //让子线程先执行一段时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //通过修改isQuit来中断线程
        isQuit = true;
        //让子线程先结束
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程任务完成");

    }
}

打印的结果如下:

2.通过Thread类提供的interrupted()方法来中断线程

public class Demo11_Interrupted02 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            System.out.println("是否中断:" + Thread.currentThread().isInterrupted());
            System.out.println("线程任务完成");
        });

        // 启动线程
        thread.start();
        //让子线程先执行一段时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //中断线程,修改Thread的中断标志
        thread.interrupt();
        //让子线程先结束
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程任务完成");
    }
}

当我们采用如上的代码来进行线程的中断,打印的结果如下:

 因为当前子线程的状态处于sleep()或者堵塞状态,这个时候线程中断的状态是sleep()或者堵塞状态,比如现实情境下张三学习1秒,睡觉1分钟,我们打断张三的状态,大概率张三在睡觉,我们打断的是张三睡觉的状态.

那么到底该怎么进行线程的中断呢?我们看到打印的结果的时候看到有异常的捕获,其实我们可以想到在异常捕获的时候加上一个break即可.

public class Demo11_Interrupted02 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }

            }
            System.out.println("是否中断:" + Thread.currentThread().isInterrupted());
            System.out.println("线程任务完成");
        });

        // 启动线程
        thread.start();
        //让子线程先执行一段时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //中断线程,修改Thread的中断标志
        thread.interrupt();
        //让子线程先结束
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程任务完成");
    }
}

3.获取当前线程

方法说明
public static Thread currentThread();
返回当前线程对象的引用

public class ThreadDemo {
    public static void main(String[] args) {
        //获取到的是主线程main
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

4.休眠当前线

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

sleep方法只是保证在这个时间段内进行休眠,不被调度到CPU运行,并不是很精准

5.主动让出CPU---yield()方法

public class Demo15_Yield {
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
            while (true){
                System.out.println("我是张三");
//                Thread.yield();
            }
        });
        Thread thread2=new Thread(()->{
            while (true){
                System.out.println("我是李四");
            }
        });
        thread1.start();
        thread2.start();

    }
}

当注释掉yield()方法的打印是这样的,基本上都是一半一半的打印.

当使用yield()方法,明显李四的打印大于张三

结论:yield 不改变线程的状态, 但是会重新去排队

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

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

相关文章

设计模式——适配器模式(类适配器、对象适配器)

是什么&#xff1f; 我们平时的有线耳机接口分为USB的和Type-C的接口&#xff0c;但是手机的耳机插口却只有一个&#xff0c;像华为的耳机插口现在基本都是Type-c的&#xff0c;那如果我们现在只有USB接口的耳机怎么办呢&#xff0c;这个时候就需要使用到一个转换器&#xff0c…

数据汇总从20s优化至2s(小经验)

目录 项目背景&#xff1a; 数据汇总访问时间长的问题&#xff1a; 解决方案&#xff1a; 1.创建参与访问表 2.使用redis进行优化 总结&#xff1a; 项目背景&#xff1a; 一个简单的抽奖系统&#xff0c;当要统计每天的参与访问&#xff0c;总的参与访问 数据汇总访问时…

运维高可用架构的 6 大常规方案

在介绍高可用架构的方案之前&#xff0c;先说一下什么是高可用架构&#xff0c;高可用架构应具备但不限于以下特征&#xff1a; 主从切换 很好理解&#xff0c;当其中一台机器的服务宕机后&#xff0c;对于服务调用者来说&#xff0c;能够迅速的切换到其他可用服务&#xff0c;…

俩小伙一晚上写了个 AI 应用,月入两万??(文末附开发教程)

开发出一款能够与 AI 对话生成和编辑思维导图的工具&#xff0c;听起来似乎只能是一群专业的 AI 背景团队花费大量的时间和精力训练模型&#xff0c;打磨应用才能完成的事情。 但是&#xff0c;两名大学生却在一夜之间完成了&#xff0c;就像炼金术士将庸俗的材料转化成黄金一…

吃掉脂肪,狂减33斤!午餐菜单一次性公开!

一周高效减脂蔬菜沙拉&#xff5c;上班族学生党减脂备餐便当 &#x1f618;我是4个月瘦30斤&#xff0c;减脂迫在眉睫&#xff0c;巨掉秤好吃的蔬菜沙拉来咯&#xff01;肉类提前备好放冷冻&#xff0c;工作日只需10分钟搞定&#xff0c;做减脂早午晚餐都可以哦&#xff5e; …

Mysql 中left join时 on、and、where区别

1、准备两张表student与class表 student class 2、left join on左连接 select * from student s left join class c on s.classId c.id 左表数据全部显示&#xff0c;关联到的右表数据显示&#xff0c;没有显示null 3、left join on ... and对左表student进行条件筛选 …

解决echarts图表随窗口宽度变化而改变图表的大小

文章目录 前言一、演示前后对比效果二、解決方法1.在代码结尾加上监听方法2.示例 三、总结扩展问题 前言 很多同学在使用echarts时遇到了浏览器窗口大小发生变化时&#xff0c;图表大小没有自适应窗口的宽度&#xff0c;下面我将对比演示随着窗口大小变化&#xff0c;echarts图…

(四)【平衡小车制作】陀螺仪MPU6050

一、硬件结构 1.什么是陀螺仪&#xff1f; 陀螺仪是用于测量或维护方位和角速度的设备。它是一个旋转的轮子或圆盘&#xff0c;其中旋转轴可以不受影响的设定在任何方向。当旋转发生时&#xff0c;根据角动量守恒定律&#xff0c;该轴的方向不受支架倾斜或旋转的影响。 2.MPU…

在Ubuntu18.04中安装uWebSockets库

目录 1.下载uWebSockets库2.下载uSockets3.安装openssl开发包4.编译首先说明这里使用的Ubuntu版本为18.04。 1.下载uWebSockets库 下载uWebSockets库有两种方式,一是终端,从Github中克隆uWebSockets库到Ubuntu本地文件夹,二是打开uWebSockets库下载链接自己下载到Windows,然…

Python实现将快手个人主页的视频进行无水印下载

前言 本文是该专栏的第26篇,后面会持续分享python的各种干货知识,值得关注。 笔者之前有详细介绍快手滑块验证码的识别方法(Python如何解决“快手滑块验证码”(4)),感兴趣的同学可往前翻阅查看。需要注意的是,滑块验证码的缺失距离需要根据你自己电脑的分辨率进行微调,…

STM32 i2c 驱动0.42寸OLED

STM32 i2c 驱动0.42寸OLED&#xff0c;这是个项目中使用的方案&#xff0c;调试也是比较费劲&#xff0c;主要是取字模和代码的匹配&#xff0c;下个篇章中详解一下取字模的过程&#xff0c;在这个方案中的使用。 本文使用的测试代码 STM32i2c驱动0.42寸OLED&#xff0c;SSD1…

体验 ChatGLM-6B

体验 ChatGLM-6B 1. 什么是 ChatGLM-6B2. Github 地址3. 安装 Miniconda34. 创建虚拟环境5. 安装 ChatGLM-6B6. 启动 ChatGLM-6B7. 访问 ChatGLM-6B8. API部署9. 命令行部署10. 其他&#xff0c;修改使用显存大小 1. 什么是 ChatGLM-6B ChatGLM-6B 是一个开源的、支持中英双语…

面了一个4年经验的测试工程师,自动化都不会也要18k,我真是醉了...

在深圳这家金融公司也待了几年&#xff0c;被别人面试过也面试过别人&#xff0c;大大小小的事情也见识不少&#xff0c;今天又是团面的一天&#xff0c; 一百多个人都聚集在一起&#xff0c;因为公司最近在谈项目出来面试就2个人&#xff0c;无奈又被叫到面试房间。 整个过程…

音频焦点使用及原理

音频焦点使用及原理 本博客代码基于Android 10源码 为什么会有音频焦点这一概念&#xff1f; 在Android音频领域中&#xff0c;应用层所有的App播放音频&#xff0c;最终都是走到音频回播线程PlaybackThread中&#xff0c;如果多个App都走到同一个PlaybackThread中去&#xff0…

chat错误代码1020是什么原因导致的-解决chat错误代码1020

ChatGPT拒绝访问1020是什么原因 ChatGPT拒绝访问1020可能是因为服务器故障、网络连接问题或者人工智能模型正在进行训练或调整等原因所致。一般来说&#xff0c;ChatGPT会在进行维护、升级或训练时暂停服务&#xff0c;这可能导致用户在访问ChatGPT时遇到拒绝访问的情况。在该…

2023年淮阴工学院五年一贯制专转本财务管理基础考试大纲

2023年淮阴工学院五年一贯制专转本财务管理基础考试大纲 一、考核对象 本课程的考核对象为五年一贯制高职专转本财务管理专业入学考试普通在校生考生。 二、考核方式 本课程考核采用闭卷笔试的方式。 三、命题依据及原则 1、命题依据 本课程考核命题教材为靳磊编著&…

2023年会展服务研究报告

第一章 行业概况 会展行业是指一系列与会议、展览、展示相关的服务和经济活动的总称&#xff0c;是加强企业间交流、促进合作和推动经济发展的重要手段。该行业涉及广泛&#xff0c;包括会议和展览的组织、场地租赁和设计、活动策划和执行、展品运输和咨询服务等各个环节。随着…

每个人都可以用来提高生产力的「项目管理策略」

与以往相比&#xff0c;越来越多的公司为员工提供混合办公&#xff08;办公室工作远程办公&#xff09;和远程办公的选择&#xff0c;这实际是出于对效率的强需求。 尽管员工的工作时间增加了&#xff0c;但办公室等工作场所的工作效率正在以几十年来最快的速度下降。专家们列…

一起了解可以用手机操作显示的BOD水质检测仪

什么叫BOD&#xff08;生化需氧量&#xff09;&#xff1a; BOD是指表示水中有机化合物等需氧物质含量的一个综合指标。当水中所含有机物与空气接触时&#xff0c;由于需氧微生物的作用而分解&#xff0c;使之无机化或气体化时所需消耗的氧量。 生化需氧量&#xff0c;以毫克/升…

震惊,为了学会泛型类竟做这种事?!

上一节&#xff0c;我们基本学会了Java泛型类的用法。 传送门&#xff1a;彻底弄懂Java的泛型 - 泛型类 这一节&#xff0c;我们转变一下风格&#xff0c;具体是什么风格呢&#xff0c;你马上就懂了。 宝子们&#xff0c;欢迎大家来到我们的泛型直播间&#xff0c;这一讲呢&a…