Java之多线程初阶2

目录

一.上节内容复习

1.进程和线程的区别

2.创建线程的四种方式

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

1.多线程的优点

2.代码实现

三.Thread类常用的方法

1.Thread类中的构造方法

2.Thread类中的属性

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

2.演示isDaemon()

3.演示isAlive()

4.演示getState()

3.Thread类中的方法

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

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

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

3.获取当前线程

4.休眠当前线

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


一.上节内容复习

上节内容指路: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 不改变线程的状态, 但是会重新去排队

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT写文章效果-ChatGPT写文章原创

ChatGPT写作程序&#xff1a;让文案创作更轻松 在当前数字化的时代&#xff0c;营销推广离不开文案创作。然而&#xff0c;写作对许多人来说可能是一项耗时而枯燥的任务。如果您曾经为写出较高质量的文案而苦恼过&#xff0c;那么ChatGPT写作程序正是为您而设计的。 ChatGPT是…

Python 模块

目录 1.模块导入语言 1.1 import 语句 1.2 from…import 语句​编辑 2. 搜索路径 3.命名空间和作用域 4.globals() 和 locals() 函数 5.reload() 函数 6.Python中的包 7.自定义模块及其调用 7.1 创建模块及__init__.py初始化文件 7.2 __init__.py的参数__all__ …

【vite+vue3.2 项目性能优化实战】打包体积分析插件rollup-plugin-visualizer视图分析

rollup-plugin-visualizer是一个用于Rollup构建工具的插件&#xff0c;它可以生成可视化的构建报告&#xff0c;帮助开发者更好地了解构建过程中的文件大小、依赖关系等信息。 使用rollup-plugin-visualizer插件&#xff0c;可以在构建完成后生成一个交互式的HTML报告&#xf…

从血缘进化论的角度,破解婆媳关系的世纪难题

从血缘进化论的角度&#xff0c;破解婆媳关系的世纪难题 有个粉丝的留言&#xff0c;很长很复杂&#xff0c;是关于他们家的婆媳关系问题。 青木老师&#xff0c;您好&#xff0c;我也有一些问题想咨询您&#xff0c;是关于婆媳关系的&#xff0c;字数有些多&#xff0c;分开…

【ElasticSearch】EQL操作相关

文章目录 EQL操作基础语法数据准备数据窗口搜索统计符合条件的事件事件序列 安全检测数据准备查看数据导入情况获取 regsvr32 事件的计数检查命令行参数检查恶意脚本加载检查攻击成功可能性 EQL操作 EQL 的全名是 Event Query Language (EQL)。事件查询语言&#xff08;EQL&…

【问题记录】flask开发blog

文章目录 小知识点问题1. 文章标签显示错误2. 文章状态无法回显&#xff08;open)3. 用户管理页面&#xff0c;图标无法显示4. BuildError5. 用户管理添加用户&#xff0c;使用重复的用户名会报错(open)6. 添加用户&#xff0c;不上传头像会报错(open)7. 部分标签删除时报错&am…

JAVA springboot创业实践学分管理系统idea开发mysql数据库web结构计算机java编程MVC

一、源码特点 idea springboot创业实践学分管理系统是一套完善的web设计系统mysql数据库MVC模式开发&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式 开发。 JAVA springboot创业实践学分管理系统ide…

Ubuntu搜狗输入法安装指南

Ubuntu搜狗输入法安装指南 Ubuntu搜狗输入法安装指南搜狗输入法已支持Ubuntu1604、1804、1910、2004、2010Ubuntu20.04及以上安装搜狗输入法步骤 Ubuntu搜狗输入法安装指南 下载地址&#xff1a;https://shurufa.sogou.com/ 计算为amd64的选择x86_64&#xff0c;以下教程来源…

2023Java商城毕业设计(附源码和数据库文件下载链接)Spring Boot + mysql + maven + mybatis-plus

2023Java商城毕业设计Spring Boot mysql maven mybatis-plus 用户注册用户登录修改密码商品列表&#xff08;分类模糊查询&#xff09;个人信息用户信息修改订单信息添加至购物车商品列表商铺详情商品详情商铺列表 资源目录如下&#xff1a;&#xff08;源码sql文件&#xf…

Linux入门2(常用命令)

Linux入门2 Linux常用命令快捷键基础命令文件查看命令文件编辑命令进程管理命令用户管理命令 Linux常用命令 快捷键 Ctrl Alt T打开终端 Ctrl shift 加号 终端字体放大 ctrl 减号 终端字体缩小 基础命令 sudo su 进入管理员目录 exit 返回到用户目录 ls 当前目录下的文…

Illustrator如何使用基础功能?

文章目录 0.引言1.菜单栏2.工具箱 0.引言 因科研等多场景需要进行绘图处理&#xff0c;笔者对Illustrator进行了学习&#xff0c;本文通过《Illustrator CC2018基础与实战》及其配套素材结合网上相关资料进行学习笔记总结&#xff0c;本文对软件界面基本功能进行阐述。    1…

K8s 安全是云安全的未来

导语 到 2025 年&#xff0c;保护 Kubernetes (K8s) 将被认为是云安全最重要的方面。 在最成功的组织中&#xff0c;CTO 和 CISO 已经意识到 Kubernetes 安全的重要性。 但是&#xff0c;虽然 Kubernetes 已经占 CTO 云支出的很大一部分&#xff0c;但 CISO 仍然有所落后。 大…

Android Studio开发图书管理系统APP

Android Studio开发项目图书管理系统项目视频展示&#xff1a; 点击进入图书管理系统项目视频 引 言 现在是一个信息高度发达的时代&#xff0c;伴随着科技的进步&#xff0c;文化的汲取&#xff0c;人们对于图书信息的了解与掌握也达到了一定的高度。尤其是学生对于知识的渴…

asp.net基于web的学生选课成绩管理系统86程序

系统使用Visual studio.net2010作为系统开发环境&#xff0c;并采用ASP.NET技术&#xff0c;使用C#语言&#xff0c;以SQL Server为后台数据库。 本系统主要包含了“登录模块”、“系统用户管理模块”、“课程信息管理模块”、“教师信息管理模块”、“班级信息管理模块”、“…

Lattics ——一款简单易用、好看强大的知识管理工具

如何选择一款适合自己的知识管理工具&#xff1f; 对于很多用户而言&#xff0c;在追求效率的路上&#xff0c;经常需要一款适合自己的知识管理工具。然而&#xff0c;随着工具市场的发展&#xff0c;各种新兴工具层出不穷。在传统领域&#xff0c;有印象笔记、Onenote 为代表…

【笔试强训选择题】Day7.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录…

如何充分利用实时聊天系统?

随着商业和电子商务领域经历快速的数字革命&#xff0c;必须迅速适应的一个因素是我们与客户的互动方式。几年前&#xff0c;电子邮件和电话还是主要的客户联系方式。如今&#xff0c;客户期望更好的服务和更即时的沟通。实时聊天支持系统可以解决此问题&#xff0c;如SaleSmar…

IntelliNode:Node.js大模型访问统一接口库【Gen AI】

使用最新的 AI 模型更新你的应用程序可能具有挑战性&#xff0c;因为它涉及了解不同 AI 模型的复杂性并管理许多依赖项。 IntelliNode 是一个开源库&#xff0c;旨在通过提供统一且易于使用的界面来解决集成 AI 模型的挑战。 这使开发人员能够快速构建 AI 原型并使用高级 AI 功…

CompletableFuture

线程基础知识复习 大神&#xff1a;Doug Lea java.util.concurrent java.util.concurrent.aomic Java.util.concurrent.locks 硬件 摩尔定律&#xff1a; 它是由英特尔创始人之一 Gordon Moore(戈登摩尔)提出来的。其内容&#xff1a; 当价格不变是&#xff0c;集成电路…

python相对路径与绝对路径

9.1 Python 绝对路径与相对路径 - 知乎 (zhihu.com) 目录 1. 绝对路径 1.1 概念 1.2 用绝对路径打开文件 1.2 相对路径 1.3 python路径表示的斜杠问题 1. 绝对路径 1.1 概念 绝对路径 指完整的描述文件位置的路径。绝对路径就是文件或文件夹在硬盘上的完整路径。 在 Win…