多线程基础篇(多线程案例)

文章目录

  • 多线程案例
      • 1、单例模式
        • 1)饿汉模式
        • 2)懒汉模式
        • 3)线程安全吗??
        • 4)解决懒汉模式线程安全问题
        • 5)解决懒汉模式内存可见性问题
      • 2、阻塞队列
        • 1) 阻塞队列是什么?
        • 2) 生产者消费者模型
          • 1.生产者消费者模型的优势
          • 2.标准库中的阻塞队列
        • 3) 拟实现阻塞队列
      • 3、定时器
        • 1) 标准库中的定时器
        • 2) 模拟实现定时器
      • 4、线程池
        • 1) 工厂模式
        • 2) 标准库中的线程池
          • 1.ThreadPoolExecutor类
        • 3) 模拟实现线程池

多线程案例

1、单例模式

单例模式是校招中最常考的设计模式之一。

啥是设计模式?
设计模式好比象棋中的 “棋谱”. 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有
一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照
这个套路来实现代码, 也不会吃亏.

应用场景:有时候,希望对象在程序中只有一个实例(对象),也就是说只能new一次。由此就出现了单例模式。

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.

单例模式具体的实现方法,分成“懒汉”和“饿汉”两种。

1)饿汉模式

含义:当前代码中是比较迫切的,在类加载的时候就立刻,把实例给创建出来。

class Singleton {private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}
public class Demo3 {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();//Singleton s3 = new Singleton();       无法创建出来,由于上面的构造方法是private的System.out.println(s1 == s2);//System.out.println(s1 == s3);}
}

上述代码的执行时机,是 Singleton 类被 jvm 加载的时候。Singleton类会在 JVM 第一次使用的时候被加载。也不一定是在程序一启动的时候.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用static修饰的雷属性,由于每个类的类对象是单例的。类对象的属性(static),也就是单例的了。

单例模式中对我们的代码做出一个限制:禁止别人去new这个实例,也就是把构造方法改成private。

此时,我们要是去new这个实例,就会报错。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这时,我们能够使用的实例有且只有一个,就是类字被加载出来的同时,会进行实例化。


2)懒汉模式

含义:在真正用到的时候来进行创建实例,不用到则不会产生其他开销。(更推荐使用)

class SingletonLazy {private static SingletonLazy instance = null;private SingletonLazy() {}public static SingletonLazy getInstance() {if(instance == null) {instance = new SingletonLazy();}return instance;}
}
public class Demo4 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

懒汉模式与饿汉模式极其相似,只是在实例化的时机上有所不同。


3)线程安全吗??

接下来我们要去考虑 懒汉模式 和 恶汉模式 在多线程模式下会不会出问题呢????在多线程调用 getInstacne 的情况下,哪个模式是线程安全的(没有Bug)??

对于饿汉模式:多个线程同时读取同一个变量,就不会出现线程安全问题。

对于懒汉模式:则会出现线程安全问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🎋 简单理解:当我们按照顺序来执行,就会发现,此时创建出两个对象,程序不再是单例模式了!!

🎋 深入解读:第二次 new 操作,就把原来 instacne 的引用给修改了,之前 new 出来的对象就立刻被 gc 回收了,最后只剩下一个对象。

在通常印象中,new 一个对象并不是一个很复杂的过程,但实际上 new 一个对象开销是非常大的!!!!如果服务器一启动就需要加载 100G 的数据存到内存中,都是通过一个对象来管理,那我们 new 这个对象时,就需要消耗 100G 的内存,花费很长时间。


4)解决懒汉模式线程安全问题

提到如何解决线程安全问题,想到的就是加锁以及内存可见性问题。

先考虑一种加锁方式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这种写法是错误的,没有解决上述线程安全问题。因为这个操作本身就是原子的,再加锁也起不到实质上的作用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正确加锁方式是把锁加到外面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
在这里插入图片描述

如图所示,此时再执行线程2的时候,就能发现 instance 是一个非 null 的值。

上述代码还会出现一个问题,程序中频繁的出现加锁操作。

加锁是一个成本很高的操作,加锁可能会引起阻塞等待。因此得到加锁的基本原则就是:非必要不加锁。不能无脑加锁,频繁加锁会影响程序执行效率。


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

思考:后续每次调用getInstance都要加锁了,是必要的嘛???

不是的。每次调用,都会加锁。实际上只有第一次调用有必要加锁,后面都不需要。这里把不该加锁的地方给加锁了,就会很影响程序的效率。

懒汉模式线程不安全,主要是在首次 new 对象的时候,才存在问题。一旦把对象new好了之后,后续再调用getInstance,都没事了!!

上述代码在首次调用和后续调用都加锁了,因此降低了程序的执行效率。

对代码进行调整:先判定是否要加锁,再决定是不是真的加锁

public static SingletonLazy getInstance() {if (instacne == null) {synchronized (SingletonLazy.class){if(instance == null) {instance = new SingletonLazy();}}}return instance;
}

可以看到,在外层又加了一个判断条件。第一个条件是为了判断是否要加锁,第二个条件是判断是否要创建对象

同一个条件,写了两遍,这个合理嘛???

合理的不得了。

如果在单线程中,两个同样的判定,结果一定是一样的。但对于多线程来说,可能会涉及到阻塞。阻塞多久并不知道,第二个条件和第一个条件之间可能会间隔非常长的时间(沧海桑田),在这个很长的时间间隔里,可能别的线程就把 instance 给改了。


5)解决懒汉模式内存可见性问题
public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;
}

在多线程模式中,第一个线程,在修改完 instance 之后,就结束了代码块,释放了锁。第二个线程就能够获取到锁了,从阻塞中恢复了。

问题出现了,第二个线程这里进行的“读操作”一定能读取到第一个线程修改之后的值吗??

答案是不一定,可能会出现内存可见性问题。这里只是分析了一下,可能存在这样的风险。编译器实际上在这个场景中是否会触发优化,打上问号。因此,给 instance 加上一个 volatile 是更为稳妥的做法。

加上 volatile 还有另外一个用途,就是避免此处赋值操作的指令重排序!

指令重排序:也是编译器优化的一种手段,是保证原有执行逻辑不变的前提下,对代码执行顺序进行调整。使调整之后的执行效率提高。

单线程中,这样的重排序一般没事。但是多线程下,就会出现问题。

instance = new SingletonLazy()

像上面这句代码,一句代码在编译器中由三句指令构成。

  1. 给对象创建出内存空间,得到内存地址。
  2. 在空间上调用构造方法,对对象进行初始化。
  3. 把内存地址,赋值给 instance 引用。

在这里插入图片描述

给 instacne 加上 volatile 之后,此时针对 instance 进行的赋值操作,就不会产生上述的指令重排序了。必须按照 1 2 3 的顺序执行,不会出现 1 3 2 的执行顺序。

2、阻塞队列

1) 阻塞队列是什么?

阻塞队列是一种特殊的队列,带有阻塞功能。也遵守 “先进先出” 的原则。

阻塞队列能是一种线程安全的数据结构,并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素。
  • 当队列空的时候,,继续出队列也会阻塞,直到有其他线程往队列中插入元素。

阻塞队列的一个典型应用场景就是 “生产者消费者模型”。这是一种非常典型的开发模型


2) 生产者消费者模型

在该模型中,生产者负责生产数据,并将其放入一个缓冲区中。消费者负责从缓冲区中取出数据并进行消费。

本质上来说就是通过一个容器来解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

1.生产者消费者模型的优势

减少任务切换开销,减少锁竞争。

  1. 阻塞队列会使生产者和消费者之间 解耦合

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  2. 平衡了生产者和消费者的处理能力。(削峰填谷)

阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

服务器收到的来自于客户端/用户的请求,不是一成不变的。可能会因为一些突发事件,引起请求数目暴增~~

一台服务器,同一时刻能处理的请求数量是有上限,不同的服务器承担的上限是不一样的。

在一个分布式系统中,就经常会出现,有的机器能承担的压力更大,有的则更小~~

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


正因为生产者消费者模型,这么重要,虽然阻塞队列只是一个数据结构,但我们会把这个数据结构(阻塞队列)单独实现成一个服务器程序并且使用单独的主机/主机集群来部署

此时,这个所谓的则塞队列,就进化成了“消息队列”。

2.标准库中的阻塞队列

java标准库中已经提供了现成的阻塞队列的实现了,有基于链表、堆、优先级、数组实现的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Array 这个版本的速度要更快,前提是得先知道最多有多少元素。(对于 Array 版本来说,频繁扩容是一个不小的开销)

如果不知道有多少元素,使用 Linked 更合适。

对于BlockfingQueue来说,offer 和 poll 不带有阻塞功能,而 put 和 take 带有阻塞功能。

3) 拟实现阻塞队列

这里我们基于数组,循环队列来简单实现一个阻塞队列。

循环队列就像是把数组首尾连接起来,组成一个环状,以此提高使用效率。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面简单实现阻塞队列的两个主要功能puttake

  • put:入队列。
  • take:出队列。

阻塞队列的构成:

  1. 循环队列

    • 用 size 来标记队列长度,可以判断队列是否为满。
    • 实现循环效果,头(head) 尾(hail) 相连。
  2. 实现阻塞

    • 入队列时。当队列满的时候,再进行 put 就会产生阻塞。
    • 出队列时。当队列空的时候,再进行 take 也会产生阻塞。
  3. 写完代码,需要考虑线程安全问题。(内存可见性、加锁)

    • 内存可见性
    • 加锁
  4. wait 搭配 while 循环使用

    原因:

    • 意外被 interrupted 唤醒。
    • 多线程下,出现线程安全问题。

下面是实现代码:

class MyBlockingQueue {private String[] items = new String[1000];volatile private int head = 0;volatile private int tail = 0;volatile private int size = 0;public void put (String elem) throws InterruptedException {synchronized (this) {while (size >= items.length) {//队列满,开始阻塞等待。System.out.println("队列满,开始阻塞等待!!!");this.wait();//return;}items[tail] = elem;tail ++;if (tail >= items.length) {tail = 0;}size ++;this.notify();}}public String take () throws InterruptedException {synchronized (this) {//判断队列是否为空,若为空则不能出队列。while (size == 0) {//队列空,开始阻塞等待System.out.println("队列空,开始阻塞等待!!");this.wait();//return null;}String elem = items[head];head ++;if (head >= items.length) {head = 0;}size --;//使用 notify 来唤醒队列满的阻塞情况。this.notify();return elem;}}
}public class Demo1 {public static void main(String[] args) throws InterruptedException {MyBlockingQueue queue = new MyBlockingQueue();//生产者Thread t1 = new Thread(() ->{int count = 0;while (true) {try {queue.put(count + "");System.out.println("生产元素:" + count);count ++;//Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//消费者Thread t2 = new Thread(() -> {while (true) {try {String count = queue.take();System.out.println("消费元素:" + count);Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}
}/*结果
先生产1000个元素,阻塞等待消费元素。
之后消费一个元素即生产一个元素。
*/

3、定时器

什么是定时器?

⏰ 定时器也是软件开发中的一个重要组件。类似于一个 “闹钟”。达到一个设定的时间之后,就执行某个指定好的代码

1) 标准库中的定时器
  • 标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
  • schedule 包含两个参数。第一个参数 (TimerTask) 指定即将要执行的任务代码, 第二个参数 (delay) 指定多长时间之后执行 (单位为毫秒).
Timer timer = new Timer();
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("定是任务执行");}
}, 3000);System.out.println("程序开始运行");

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当我们运行代码,就会发现,打印完 hello 程序并没有结束,而是在持续执行

原因就在于:Timer 内部,有自己的线程。为了保证随时可以处理新安排的任务,这个线程会持续执行。并且这个线程还是个前台线程。

上述代码中,可以看到 schedule 有两个参数, 其中的一个参数 TimeTask 类似于 Runnable。在那里会记录时间,以及执行什么样的任务。

下图中的源代码 TimerTask 也实现了 Runnable 接口。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


2) 模拟实现定时器

一个定时器里,是可以有很多个任务的!!

先要能够把一个任务给描述出来,再使用数据结构把多个任务组织起来。

定时器的构成思路:

  1. 创建一个TimerTask这样类,表示一个任务。这个任务,就需要包含两方面,任务的内容,任务的实际执行时间。

  2. 使用一定的数据结构,把多个 TimerTask 给组织起来。

    如果使用 List (数组,链表)组织 TimerTask 的话。在任务特别多的情况下,如何确定哪个任务,何时能够执行呢?这样就需要搞一个线程,不停的对上述的List进行遍历。看这里的每个元素,是否到了时间,时间到就执行,时间不到,就跳过扫描下一个。

    这个思路并不科学!!如果这些任务的时间都还为时尚早。在时间到达之前,此处的扫描线程就需要一刻不停的反复扫描。

    这里可以使用优先级队列,来组织所有的任务。因为队首元素,就是时间最小的任务

  3. 搞一个扫描线程,负责监控队首元素的任务是否时间到,如果到时间了,就执行 run 方法来完成任务。


注意问题:

  1. 代码中所使用的优先级队列不是线程安全的!!在此需要针对 queue 的操作,进行加锁。

  2. 扫描线程中(阻塞等待),直接使用 sleep 进行休眠,是否合适呢?? 非常不合适的!!!!

    • sleep 进入阻塞后,不会释放锁。影响其他线程去执行这里的 schedule.
    • sleep 在休眠的过程中,不方便提前中断。(虽然可以使用 interrupt 来中断。但是 interrupt 意味着线程应该要结束了)
    • 这里可以使用 wait 和 notify 来阻塞和唤醒程序。
      • 当队列为空时,需要阻塞等待。当确定了最早需要执行任务时,也需要阻塞等待(等待任务执行)
      • 当 queue 中添加新任务时,就需要唤醒正在阻塞等待的程序,来重新进行判定哪个是最新的任务。
  3. 随便写一个类,它的对象都能放到优先级队列中嘛?有没有什么特别的要求?

    要求放到优先级队列中的元素,是“可比较的"

    通过 Comparable 或者 Comparator 定义任务之间的比较规则。此处,我们在 MyTimerTask 这里,让他实现 Comparable。


下面是实现代码:

import java.util.PriorityQueue;//模拟实现定时器
class MyTimerTask implements Comparable<MyTimerTask>{//执行任务时间private long time;//执行任务内容private Runnable runnable;public MyTimerTask(Runnable runnable, long delay) {time = System.currentTimeMillis() + delay;this.runnable = runnable;}public long getTime() {return time;}public Runnable getRunnable() {return runnable;}@Overridepublic int compareTo(MyTimerTask o) {return (int)(o.time - this.time);}
}class MyTimer {//使用优先级队列private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();//定义锁对象private Object locker = new Object();public void schedule (Runnable runnable, long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable, delay);queue.offer(task);locker.notify();}}public MyTimer() {Thread t = new Thread(() -> {while (true) {synchronized (locker) {try {if (queue.isEmpty()) {locker.wait();}long curTime = System.currentTimeMillis();MyTimerTask task = queue.peek();if (curTime >= task.getTime()) {queue.poll();task.getRunnable().run();} else {locker.wait(task.getTime() - curTime);}} catch (InterruptedException e) {throw new RuntimeException(e);}}}});t.start();}
}
public class Demo2 {public static void main(String[] args) {MyTimer myTimer = new MyTimer();myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3");}},3000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2");}},2000);myTimer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1");}},1000);System.out.println("开始执行!!!");}
}

4、线程池

池(Poll) 是一个非常重要的思想方法。包括内存池、进程池、连接池、常量池……这些“池”概念上都是一样的。

线程池可以管理和重用多个线程,以提高应用程序的性能和资源利用率。线程池维护了一个线程队列,当需要执行任务时,可以从线程池中获取一个空闲线程来执行任务,而不需要每次都创建新的线程。这样可以减少线程创建和销毁的开销,并且可以限制并发线程的数量,避免资源耗尽

为啥,从池子里取,就比从系统这里创建线程更快更高效呢?

  1. 如果是从系统这里创建线程,需要调用系统api。由操作系统内核,来完成线程的创建过程。(这里的内核是给所有的进程来提供服务的,除了创建线程这个任务外,还有其他任务。因此效率低下)不可控的!!
  2. 如果是从线程池这里获取线程,上述的内核中进行的操作,都提前做好了,现在的取线程的过程,纯粹的用户代码完成(纯用户态)可控的!!

因此线程池最大的好处就是减少每次启动线程、销毁线程的损耗


1) 工厂模式

什么是工厂模式??

工厂模式是一种创建对象的设计模式,它通过将对象的创建委托给一个工厂类来解决对象创建的问题。工厂模式提供了一种灵活的方式来创建对象,使得客户端不需要使用new关键字来创建对象,而是通过调用工厂类的方法来获得所需的对象

说完概念后,再来简单描述一下工厂模式。

一般创建对象,都是通过 new ,通过构造方法。但是构造方法存在重大缺陷!!此时就可以使用工厂模式来解决问题了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用工厂模式来解决上述(无法重载)问题。不使用构造方法了。而是使用普通的方法来构造对象。这样的方法名字就可以是任意的了。普通方法内部,再去 new 对象。由于普通方法目的是为了创建出对象来,因此这样的方法一般得是静态的

//工厂模式
class Point {public static Point makePointXY(double x, double y) {  //工厂方法return new Point();}public static Point makePointRA(double r, double a) {  //工厂方法return new Point();}
}
public class Demo1 {public static void main(String[] args) {Point point = Point.makePointXY(5, 6);Point point = Point.makePointAR(7, 8);}
}

我们再来看一看下面源代码中是如何运用工厂模式的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在使用的时候,就直接去调用方法即可。

Executors:被称为“工厂类“。

newFixedThreadPool:被称为“工厂方法”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


2) 标准库中的线程池
  • 使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
  • 返回值类型为 ExecutorService.
  • 线程池对象创建好后,可以通过 submit方法,把任务添加到线程池中.
ExecutorService executorService = Executors.newFixedThreadPool(4);for (int i = 0; i < 1000; i++) {executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});
}
MethodsDescription
newCachedThreadPool()创建出一个线程数目动态变化的线程池.
newFixedThreadPool(int nThreads)创建一个固定线程数量的线程池.
newScheduledThreadPool(int corePoolSize)类似于定时器,在后续的某个时刻再执行。
newSingleThreadExecutor()包含单个线程(比原生创建的线程 api 更简单一点)

1.ThreadPoolExecutor类

除了上述这些线程池之外,标准库还提供了一个接口更丰富的线程池类。

为了方便使用,将上述的几个方法进一步封装,形成一个新的 ThreadPoolExecutor 类,以此更好的满足实际的需求。

我们可以在官方文档中查看 ThreadPoolExecutor 类,在 java.util.concurrent 这个包中,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


谈谈Java标准库里的线程池构造方法的参数和含义

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


当线程池无法接受新任务时,会调用一下方式来拒绝执行任务。

第一个拒绝策略

ThreadPoolExecutor.AbortPolicy

默认的拒绝策略,会抛出RejectedExecutionException异常

第二个拒绝策略

ThreadPoolExecutor.CallerRunsPolicy

会将任务回退给调用者线程来执行

第三个拒绝策略

ThreadPoolExecutor.DiscardOldestPolicy

会丢弃最早加入队列的任务,然后尝试重新添加新任务

第四个拒绝策略

ThreadPoolExecutor.DiscardPolicy

会直接丢弃该任务


上述谈到的线程池

  • 一组线程池,是封装过的Executors
  • 一组线程池,ThreadPoolExecutor 原生的

二者用哪个都可以,主要还是看实际需求。


3) 模拟实现线程池

线程池中所用到的数据结构还是阻塞队列。

实现其主要功能 submit.

下面是代码实现:

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;//简单模拟实现 线程池
class MyPollThread {static BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();public void submit (Runnable runnable) throws InterruptedException {queue.put(runnable);}public static MyPollThread newMyPollThread(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(()-> {while (true) {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}return new MyPollThread();}
}public class Demo2 {public static void main(String[] args) throws InterruptedException {MyPollThread myPollThread = MyPollThread.newMyPollThread(4);for (int i = 0; i < 1000; i++) {myPollThread.submit(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " hello");}});}}
}

关于线程池还有一个问题,在创建线程池的时候,线程个数是咋来的呢??

不同的项目中,线程要做的工作,是不一样的~~

  • 有的线程的工作,是"CPU密集型",线程的工作全是运算

    大部分工作都是要在CPU上完成的.
    CPU得给他安排核心去完成工作才可以有进展~~

    如果CPU是N个核心,当你线程数量也是N的时候

    理想情况下,每个核心上一个线程.
    如果搞很多的线程,线程也就是在排队等待,不会有新的进展.

  • 有的线程的工作,是"IO密集型",读写文件,等待用户输入,网络通信

    涉及到大量的等待时间。等的过程中,没有使用 cpu 这样的线程就算更多一些,也不会给CPU造成太大的负担。

    比如CPU是16个核心,写32个线程。

    由于是IO密集的,这里的大部分线程都在等,都不消耗CPU,反而CPU的占用情况还很低~~

实际开发中,一个线程往往是一部分工作是cpu密集的,一部分工作是io密集的。此时,一个线程,几成是在cpu上运行,几成是在等待io,这说不好!
这里更好的做法,是通过实验的方式,来找到合适的线程数。通过性能测试,尝试不同的线程数目。实验过程中,找到性能和系统资源开销比较均衡的数值。

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

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

相关文章

操作系统原理-习题汇总

临近毕业&#xff0c;整理一下过去各科习题及资料等&#xff0c;以下为操作系统原理的习题汇总&#xff0c;若需要查找题目&#xff0c;推荐CtrlF或commandF进行全篇快捷查找。 操作系统原理 作业第一次作业选择题简答题 第二次作业选择题简答题 第三次作业选择题简答题 第四次…

Oracle - 多区间按权重取值逻辑

啰嗦: 其实很早就遇到过类似问题&#xff0c;也设想过&#xff0c;不过一致没实际业务需求&#xff0c;也就耽搁了&#xff1b;最近有业务提到了&#xff0c;和同事讨论&#xff0c;各有想法&#xff0c;所以先把逻辑整理出来&#xff0c;希望有更好更优的解决方案&#xff1b;…

CSS鼠标指针表

(机翻)搬运自:cursor - CSS: Cascading Style Sheets | MDN (mozilla.org) 类型Keyword演示注释全局autoUA将基于当前上下文来确定要显示的光标。例如&#xff0c;相当于悬停文本时的文本。default 依赖于平台的默认光标。通常是箭头。none不会渲染光标。链接&状态contex…

Spring的注解开发-注解方式整合MyBatis代码实现

之前使用xml方式整合了MyBatis&#xff0c;文章导航&#xff1a;Spring整合第三方框架-MyBatis整合Spring实现-CSDN博客 现在使用注解的方式无非是就是将xml标签替换为注解&#xff0c;将xml配置文件替换为配置类而已。 非自定义配置类 package com.example.Configure;import c…

yolov5检测cs2中的目标

环境介绍 系统&#xff1a;Windows11 显卡&#xff1a;4070ti cuda:11.8 配置环境 python环境 安装python的虚拟环境anaconda。Free Download | Anaconda 成功安装后可以按Win键搜索anaconda&#xff0c;可以看到桌面版和命令行版本&#xff0c;我们这里直接用命令行版本…

spring-boot入门之如何利用idea创建一个spring-boot项目

1.创建流程&#xff01;&#xff01;&#xff01; 选择新建项目&#xff0c;这里我们需要注意是基于maven建立的和java版本和jdk版本要对应 这里我们是基于web项目创建的记得选择这个框架。 2.测试程序 编写hello测试类 我们需要通过程序的入口进行启动程序。idea已经为我们自…

C++算法 —— 动态规划(7)两个数组的dp

文章目录 1、动规思路简介2、最长公共子序列3、不相交的线4、不同的子序列5、通配符匹配6、正则表达式匹配7、交错字符串8、两个字符串的最小ASCII删除和9、最长重复子数组 每一种算法都最好看完第一篇再去找要看的博客&#xff0c;因为这样会帮你梳理好思路&#xff0c;看接下…

vue项目开发环境工具-node

最近在开始接触做vue框架的前端项目&#xff0c;以前用的前端比如html&#xff0c;js&#xff0c;css等都是比较原生的&#xff0c;写好后直接浏览器打开就行。但vue跟java一样是需要编译的&#xff0c;和微信小程序类似。今天就先记录一下vue的开发运行搭建。所需工具如下 nod…

【MySQL】MySQL 官方安装包形式

MySQL 官方提供3种包&#xff1a; 1. 源码包 mysql-5.7.42.tar.gz mysql-5.7.42-aarch64.tar.gz http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.34.tar.gz http://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.42.tar.gz需要用户根据自己的CPU架构选择对应的…

vue3+ts创建前端blog项目

vue3创建blog项目 cmd创建Manually select featuresChoose Vue versionUse class-style component syntax? (Y/n)Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n)Use history mode for router?Pick a CSS pre…

Python之函数、模块、包库

函数、模块、包库基础概念和作用 A、函数 减少代码重复 将复杂问题代码分解成简单模块 提高代码可读性 复用老代码 """ 函数 """# 定义一个函数 def my_fuvtion():# 函数执行部分print(这是一个函数)# 定义带有参数的函数 def say_hello(n…

码科速送同城跑腿小程序 v3.2.8+用户端+接单端 安装测试教程

码科速送同城跑腿V3.2.8版本包含骑手端用户端小程序&#xff01;骑手端码科跑腿快速发单&#xff0c;码科速送同城跑腿小程序是一款专用于同城跑腿小程序源码&#xff0c;播播资源针对这系统安装后感觉配置比较折腾人&#xff0c;不过正常使用后基本没发现什么BUG。本版本并非开…

摄影后期图像编辑软件Lightroom Classic 2023 mac中文特点介绍

Lightroom Classic 2023 mac是一款图像处理软件&#xff0c;是数字摄影后期制作的重要工具之一&#xff0c;lrc2023 mac适合数字摄影后期制作、摄影师、设计师等专业人士使用。 Lightroom Classic 2023 mac软件特点 高效的图像管理&#xff1a;Lightroom Classic提供了强大的图…

JUC——并发编程—第四部分

理解JMM Volatile是Java虚拟机提供的轻量级的同步机制。有三大特性。 1.保证可见性 2.不保证原子性 3.禁止指令重排 定义:Java内存模型&#xff0c;是一个概念。 关于JMM的一些同步的约定: 1、线程解锁前&#xff0c;必须把共享变量立刻刷回主存. 2、线程加锁前&#x…

【AI视野·今日Robot 机器人论文速览 第四十三期】Thu, 28 Sep 2023

AI视野今日CS.Robotics 机器人学论文速览 Thu, 28 Sep 2023 Totally 37 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;****触觉力控学习策略,基于触觉的主动推理与力控用于小孔插入任务。提出了姿态控制与插入控制双策略模型。 (from 东京大学…

HTML开篇之安装VSvode(用记事本编辑HTML)

文章目录 前端开篇开篇知识点讲解1.HTML 结构1.1认识 HTML 标签1.2HTML 文件基本结构1.3标签层次结构1.4快速生成代码框架1.5用记事本写HTML1.6前端开发工具1.7下载vscode 及使用教学 大家好&#xff0c;我是晓星航。今天为大家带来的是 HTML 相关的讲解&#xff01;&#x1f6…

redis的简单使用

文章目录 环境安装与配置redis发布-订阅相关命令redis发布-订阅的客户端编程redis的订阅发布的例子 环境安装与配置 sudo apt-get install redis-server # ubuntu命令安装redis服务ubuntu通过上面命令安装完redis&#xff0c;会自动启动redis服务&#xff0c;通过ps命令确认&a…

【数组及指针经典笔试题解析】

1.数组和指针笔试题 题目1 int main(){int a[5] { 1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5};int * ptr (int * )(&a 1);printf("%d&#xff0c;%d"&#xff0c;*(a 1)&#xff0c;*(ptr - 1));return 0;}图文解析&#xff1a; int * ptr …

批量删除wordpress文章修订版本/自动草稿残留数据(3种方法)及四种方法禁用WordPress文章历史修订/自动保存/自动草稿功能

目录 1、批量删除wordpress文章修订版本/自动草稿残留数据&#xff08;3种方法&#xff09; 方法一&#xff1a;SQL命令批量删除 命令&#xff1a; 方法二&#xff1a;利用PHP代码来删除 方法三&#xff1a;利用数据库清理优化插件 WP Clean Up 或 WP Cleaner 批量删除 2…

PowerPoint如何设置密码?

PowerPoint&#xff0c;也就是PPT&#xff0c;是很多人工作中经常用的办公软件&#xff0c;而PPT和Word、Excel等一样可以设置密码保护。 PPT可以设置两种密码&#xff0c;一种是“打开密码”&#xff0c;也就是需要密码才能打开PPT&#xff1b;还有一种是设置成有密码的“只读…