详细剖析多线程3----代码案例分析

文章目录

  • 一、单例模式(校招中最常考的设计模式之⼀)
    • 1.1饿汉模式
    • 1.2懒汉模式
  • 二、阻塞队列
  • 三、定时器
  • 四、线程池
  • 五、总结



一、单例模式(校招中最常考的设计模式之⼀)

单例模式是一种设计模式,其核心思想是保证一个类只有一个实例,并提供一个全局访问点来访问这个实例。通过单例模式,可以确保在程序运行过程中只有一个实例被创建并且被不同模块共享使用。
单例模式具体的实现⽅式有很多. 最常⻅的是 “饿汉” 和 “懒汉” 两种.

1.1饿汉模式

下面这段代码,称为”饿汉模式“,是单例模式中一种简单的写法,实例在类加载的时候就创建了,创建时机非常早,相当于程序一启动,实例就创建了,就使用”饿汉“形容创建实例非常迫切,非常早。

class Singleton{
    //期望这个类只能有唯一的实例(一个进程中)
    private static Singleton instance=new Singleton();
    //static这个引用就是期望创建出唯一的实例的引用
    //static静态的指的是”类属性“,instance就是Singleton类对象里持有的属性
    //类对象就是.class文件,Singleton.class()从.class文件加载到内存中表示这个类的一个数据结构
    //每个类的类对象只存在一个,类对象的static属性自然也就只有一个了
    //因此,instance指向的这个对象就是唯一的一个对象
    private Singleton(){
        //构造方法设为私有的,其他代码就没法new了,从根本上让其他代码只能使用getInstance()方法
    }
    public static Singleton getInstance(){
        //其他代码要想使用这个类的实例,就需要通过这个getInstance()方法获取
        //不应该在其他代码中重新new这个对象,而是使用这个方法获取到现成的对象
        return instance;
    }
}
public class ThreadDemo22 {
    public static void main(String[] args) {
        //Singleton s=new Singleton();不能new,会报错
        Singleton s=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        System.out.println(s==s2);//true,多次调用也不会产生多个实例,始终是那一个实例
    }
}
  • 第一步:先创建好这个实例,通过一个静态成员持有
  • 第二步:提供一个静态的public方法,能够获取到刚才那个实例
  • 第三步:构造方法设为私有,外界没法进行new操作,让编译器真正进入检查之中,避免由于人的约定产生误会

上述代码线程是否安全?
对于”饿汉模式“,getInstance直接返回Instance实例,这个操作本质上是读操作,多个线程读取同一个变量,线程是安全的。

1.2懒汉模式

下面这段代码,是"懒汉模式"的方式实现单例模式,“懒汉模式”,创建实例的时间比较晚,只到第一次使用的时候才会创建实例。

class Singleton {
比特就业课
 private static Singleton instance = null;
 private Singleton() {}
 public static Singleton getInstance() {
 if (instance == null) {
 instance = new Singleton();
 }
 return instance;
 }
}

上面代码线程是否安全呢?
这段代码是线程不安全的,因为在getInstance方法中没有对instance的赋值操作进行同步处理,多个线程同时调用getInstance方法时可能会导致多个实例被创建。可以通过给getInstance方法加上synchronized关键字来保证线程安全。

改进后的代码:

class Singleton {
 private static Singleton instance = null;
 private Singleton() {}
 public synchronized static Singleton getInstance() {
 if (instance == null) {
 instance = new Singleton();
 }
 return instance;
 }
}

现在这段代码线程安全吗?
这段代码并不是完全线程安全的。在并发环境下,多个线程同时调用getInstance()方法时,可能会同时通过instance == null的判断,从而多个实例被创建出来。
为了改进这个问题,可以使用双重检查锁定(Double-Checked Locking)机制来确保线程安全。原因是在双重检查锁定中,只有第一次创建实例的时候才会进入同步块,其他线程都会在第一次创建实例后直接返回实例,避免了不必要的同步开销。同时,在instance变量前加上volatile关键字,可以保证多个线程之间对instance变量的可见性,避免出现指令重排的问题。

改进后的线程安全代码:

class SingletonLazy{
    //这个引用指向唯一实例,这个引用先初始化为null,而不是立即创建实例
    private static volatile SingletonLazy instance=null;
    public static SingletonLazy getInstance(){
        //如果instance为null,说明是首次调用,首次调用要考虑线程安全问题,就要加锁
        //如果非null,就说明是后续的调用,就不必加锁了
        //双重校验锁
        //外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了
        if(instance==null){
            //如果首次调用getInstance,那么此时instance引用为空,就会进入if条件,从而把实例创建出来
            //如果是后续再次调用getInstance,由于instance已经不再是null了,此时不会进入if,直接返回之前创建好的引用了(其他竞争到锁的线程就被⾥层 if 挡住了. 也就不会继续创建其他实例.)
            //这样设定,仍然可以保证该类的实例是唯一一个
            //与此同时,创建实例的时机就不是程序驱动时了,而是第一次调用getInstance的时候
            synchronized ((Singleton.class){
                if(instance==null){
                    instance=new SingletonLazy();
                }
            }
        }
        return instance;

    }
    private SingletonLazy(){}
}
public class ThreadDemo23 {
    public static void main(String[] args) {
        SingletonLazy s1=SingletonLazy.getInstance();
        SingletonLazy s2=SingletonLazy.getInstance();
        System.out.println(s1==s2);//true,指向同一个实例
    }
}

在这里插入图片描述

理解双重 if 判定 / volatile:(面试题)
1. 假设有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同⼀把锁.
2. 其中线程1 率先获取到锁, 此时线程1 通过里层的 if (instance == null) 进⼀步确认实例是否已经创建. 如果没创建, 就把这个实例创建出来.
3. 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过里层的 if (instance == null) 来确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.
4. 后续的其他线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了,从而不再尝试获取锁了. 降低了开销.
5. 在instance变量前加上volatile关键字,可以保证多个线程之间对instance变量的可见性,避免出现指令重排的问题.

二、阻塞队列

阻塞队列是⼀种特殊的队列. 也遵守 “先进先出” 的原则。
他是基于普通队列做出的扩展–
a)如果针对一个已经满了的队列进行入队列,此时入队列操作就会阻塞,一直阻塞到队列不满(其他线程出队列元素)之后。
b)如果针对一个已经空了的队列进行出队列,此时出队列操作就会阻塞,一直阻塞到队列不空(其他线程入队列元素)之后。

生产者消费者模型

生产者消费者模型是一种并发模式,其中生产者负责生产数据,而消费者负责消费数据。通过阻塞队列,生产者将生产的数据放入队列中,而消费者从队列中取出数据进行消费。当队列为空时,消费者将被阻塞直到队列中有数据;当队列满时,生产者将被阻塞直到队列有空间可用。

阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒。 (削峰填⾕)
阻塞队列可以有效地解耦生产者和消费者。

阻塞队列实现:

//实际开发中,生产者消费者模型,往往是多个生产者多个消费者
//这里的生产者和消费者往往不仅仅是一个线程,也可能是一个独立的服务器程序,甚至是一组服务器程序
class MyBlockingQueue1{
    private String[] elems=null;
    private int head=0;
    private int tail=0;
    private int size=0;
    //准备锁对象
    private Object locker=new Object();
    public MyBlockingQueue1(int capacity){
        elems=new String[capacity];
    }
    public void put(String elem) throws InterruptedException {
        //锁加到这里和加到方法上本质是一样的,加到方法上是给this加锁,此处是给locker加锁
        synchronized (locker){
            //改为while循环意味着wait唤醒之后再判定一次条件
            //wait之前判定一次,唤醒之后判定一次,再次确认,发现队列还是满着的,就继续等待
            while(size>=elems.length){
                //队列满了
                //后续需要这个代码阻塞
                locker.wait();
            }
            //新的元素要放到tail指向的位置上
            elems[tail]=elem;
            tail++;
            if(tail>=elems.length){
                tail=0;
            }
            size++;
            //入队列成功之后唤醒
            locker.notify();
        }
    }
    public String take() throws InterruptedException {
        String elem=null;
        synchronized (locker){
            while(size==0)
            {
                //队列空了
                //后续也需要让这个代码阻塞
                locker.wait();
            }
            //取出head位置的元素并返回
            elem=elems[head];
            head++;
            if(head>=elems.length){
                head=0;
            }
            size--;
            //元素出队列成功,加上唤醒
            locker.notify();
        }
        return elem;
    }
}
public class TreadDemo26 {
    public static void main(String[] args) {
        MyBlockingQueue1 queue=new MyBlockingQueue1(1000);
        // 生产者
        Thread t1 = new Thread(() -> {
            int n = 1;
            while (true) {
                try {
                    queue.put(n + "");//入队列
                    System.out.println("生产元素 " + n);
                    n++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        // 消费者
        Thread t2 = new Thread(() -> {
            while (true) {
                try {
                    String n = queue.take();//出队列
                    System.out.println("消费元素 " + n);

                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
    }
}

三、定时器

标准库中的定时器
• 标准库中提供了⼀个 Timer 类. Timer 类的核⼼⽅法为 schedule .
• schedule 包含两个参数. 第⼀个参数指定即将要执⾏的任务代码, 第⼆个参数指定多⻓时间之后执⾏ (单位为毫秒).

实现定时器
定时器的构成
• ⼀个带优先级队列(不要使⽤ PriorityBlockingQueue, 容易死锁!)
• 队列中的每个元素是⼀个 Task 对象.
• Task 中带有⼀个时间属性, 队⾸元素就是即将要执⾏的任务
• 同时有⼀个 worker 线程⼀直扫描队⾸元素, 看队⾸元素是否需要执⾏

package Thread;

import java.util.PriorityQueue;
//定时器
class MyTimerTask implements Comparable<MyTimerTask> {
    // 在什么时间点来执行这个任务.
    // 此处约定这个 time 是一个 ms 级别的时间戳.
    private long time;
    // 实际任务要执行的代码.
    private Runnable runnable;

    public long getTime() {
        return time;
    }

    // delay 期望是一个 "相对时间"
    public MyTimerTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        // 计算一下真正要执行任务的绝对时间. (使用绝对时间, 方便判定任务是否到达时间的)
        this.time = System.currentTimeMillis() + delay;
    }

    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
        // return (int) (o.time - this.time);
    }
}

// 通过这个类, 来表示一个定时器
class MyTimer {
    // 负责扫描任务队列, 执行任务的线程.
    private Thread t = null;
    // 任务队列
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    // 搞个锁对象, 此处使用 this 也可以.
    private Object locker = new Object();

    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask task = new MyTimerTask(runnable, delay);
            queue.offer(task);
            // 添加新的元素之后, 就可以唤醒扫描线程的 wait 了.
            locker.notify();
        }
    }

    public void cancel() {
        // 结束 t 线程即可
        // interrupt
    }

    // 构造方法. 创建扫描线程, 让扫描线程来完成判定和执行.
    public MyTimer() {
        t = new Thread(() -> {
            // 扫描线程就需要循环的反复的扫描队首元素, 然后判定队首元素是不是时间到了.
            // 如果时间没到, 啥都不干
            // 如果时间到了, 就执行这个任务并且把这个任务从队列中删除掉.
            while (true) {
                try {
                    synchronized (locker) {
                        while (queue.isEmpty()) {
                            // 暂时先不处理
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        // 获取到当前时间
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            // 当前时间已经达到了任务时间, 就可以执行任务了.
                            queue.poll();
                            task.run();
                        } else {
                            // 当前时间还没到, 暂时先不执行
                            // 不能使用 sleep. 会错过新的任务, 也无法释放锁.
                            // Thread.sleep(task.getTime() - curTime);
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 要记得 start !!!!
        t.start();
    }
}

public class ThreadDemo28 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        }, 3000);
    }
}

四、线程池

线程池是一种线程管理技术,用于管理和控制多个线程的执行。线程池中包含了多个线程,可以根据任务的需求动态地分配线程,从而实现线程的复用和减少线程的频繁创建和销毁,提高系统的性能和稳定性。
线程池最大的好处就是减少每次启动、销毁线程的损耗。

写一个简单的线程池代码:

1)提供构造方法,指定创建多少个线程
2)在构造方法中,把执行线程都创建好
3)有一个阻塞队列,能够持有要执行的任务
4)提供submit方法,可以添加新的任务

package Thread;

//写一个简单的线程池(更高效地利用多线程完成一系列的操作)
//直接写一个固定数目的线程池,暂时不考虑线程的增加和减少
//1)提供构造方法,指定创建多少个线程
//2)在构造方法中,把这些线程都创建好
//3)有一个阻塞队列,能够持有要执行的任务
//4)提供submit方法,可以添加新的任务

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

class  MyThreadPoolExecutor{
    //用来保存任务的队列
    private List<Thread> threadList=new ArrayList<>();
    //通过n指定创建多少个线程
    private BlockingDeque<Runnable> queue=new LinkedBlockingDeque<>(1000);
    public MyThreadPoolExecutor(int n){
        for (int i = 0; i < n; i++) {
            Thread t=new Thread(()->{
                //线程要做的事情就是把队列中的任务不停的取出来,并且进行执行
                while(true) {
                    Runnable runnable;
                    //此处的take带有阻塞功能
                    //如果队列为空,此处的take就会阻塞
                    try {
                        runnable = queue.take();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    //取出一个任务就执行一个任务即可
                    runnable.run();
                }
            });
            t.start();
            threadList.add(t);
        }
    }
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

public class ThreadDemo29 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
        for (int i = 0; i < 1000; i++) {
            int n = i;
            //此处的n是一个“事实final”变量
            //每次循环,都是一个新的n,n本身没有改变,就可以被捕获了
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    //System.out.println("执行任务" + i + " , 当前线程为: " + Thread.currentThread().getName());
                    //上面那样写是会报错的,变量捕获要保证i不变,但是i会改变
                    //回调函数访问当前外部作用域的变量就是变量捕获
                    System.out.println("执行任务" + n + " , 当前线程为: " + Thread.currentThread().getName());
                }
            });
        }
    }
}

//关于执行效果,多个线程间的执行是不确定的,某个线程取到了某个任务,但是并非立即执行,这个过程中另一个线程就插到前面了

ThreadPoolExecutor 提供了更多的可选参数, 可以进⼀步细化线程池⾏为的设定.
在这里插入图片描述
如果举例来理解,就是公司招聘员工忙不过来,准备招聘实习生做事。

int corePoolSize: 核心线程数(正式员⼯, ⼀旦录⽤, 永不辞退)
int maximumPoolSize:最大线程数(正式员⼯ + 实习生)
long keepAliveTime: 保存存活时间(实习生允许的空闲时间)
TimeUnit unit: 时间单位(s,min,ms,hour…)

在这里插入图片描述
这是ThreadPoolExecutor最重要的参数,面试常考。
RejectedExecutionHandler: 拒绝策略

如果任务量超出负荷了新的任务接下来怎么处理?此时就需要用到这个拒绝策略。
下面是四种拒绝策略----
在这里插入图片描述

◦ AbortPolicy(): 超过负荷, 新旧任务都不执行,继续添加任务,直接抛出异常.
◦ CallerRunsPolicy(): 新的任务会执行,但不是线程池执行,而是由任务调⽤者(添加新任务的线程)负责执行.
◦ DiscardOldestPolicy(): 丢弃队列中最⽼的任务,执行新任务.
◦ DiscardPolicy(): 丢弃新来的任务.线程池不执行,调用者也不执行.


五、总结

线程的优点

  1. 创建⼀个新线程的代价要⽐创建⼀个新进程⼩得多
  2. 与进程之间的切换相⽐,线程之间的切换需要操作系统做的⼯作要少很多
  3. 线程占⽤的资源要⽐进程少很多
  4. 能充分利⽤多处理器的可并⾏数量
  5. 在等待慢速I/O操作结束的同时,程序可执⾏其他的计算任务
  6. 计算密集型应⽤,为了能在多处理器系统上运⾏,将计算分解到多个线程中实现
  7. I/O密集型应⽤,为了提⾼性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

最后,码字不易,如果觉得对你有帮助的话请点个赞吧,关注我,一起学习,一起进步!

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

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

相关文章

Three.js阴影贴图

生成阴影贴图的步骤如下&#xff1a; 从光位置视点&#xff08;阴影相机&#xff09;创建深度图。从相机的角度进行屏幕渲染在每个像素点&#xff0c;将阴影相机的MVP矩阵计算出的深度值与深度图值进行比较如果深度图值较低&#xff0c;则说明该像素点存在阴影 &#xff0c;因…

html5如何在使用原生开发的情况下实现组件化

我们知道如何在vue/react中使用组件化开发&#xff0c;那么如果只是一个简单的界面&#xff0c;一个HTML就搞定的事情&#xff0c;你还会去新建一个vue/react项目吗&#xff1f; 在使用原生HTML开发时&#xff0c;我们也会遇到一些常见的功能、模块&#xff0c;那么如何在原生…

【APUE】网络socket编程温度采集智能存储与上报项目技术------多路复用

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

无锡国家集成电路设计中心某公司的单锂小电机直流电机H桥驱动电路

H桥驱动 L9110S是一款直流电机驱动电路&#xff0c;适合单节锂电池应用。输出电流0.4A。价格约3毛。 推荐原因&#xff1a; 某些人应该知道这个地方&#xff0c;大多数人应该不知道这个地方&#xff0c;所以推荐一下。 这个地方去过几次&#xff0c;某公司与某方走的“近”&…

在同一个局域网如何共享打印机和文件

1.在连接了打印机的主机上设置 1.1启用windows共享 打开网络与共享中心&#xff0c;点击“更改高级共享设置” 选择&#xff1a; “启用网络发现”“启用文件和打印机共享”“启用共享以便可以访问网络的用户可以读取和写入公用文件夹中的文件” 打开控制面板&#xff0c;选…

C#将Console写至文件,且文件固定最大长度

参考文章 将C#的Console.Write同步到控制台和log文件输出 业务需求 在生产环境中&#xff0c;控制台窗口不便展示出来。 为了在生产环境中&#xff0c;完整记录控制台应用的输出&#xff0c;选择将其输出到文件中。 但是&#xff0c;一次性存储所有输出的话&#xff0c;文件会…

NASA数据集——加拿大西北地区(NWT)2014 年被野火烧毁的北方森林的实地数据

ABoVE: Characterization of Carbon Dynamics in Burned Forest Plots, NWT, Canada, 2014 简介 文件修订日期&#xff1a;2019-04-12 数据集版本: 1 摘要 该数据集提供了加拿大西北地区&#xff08;NWT&#xff09;2014 年被野火烧毁的北方森林的实地数据。在 2015 年的实…

(学习日记)2024.04.03:UCOSIII第三十一节:信号量函数接口讲解

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

Linux (Ubuntu)- mysql8 部署

目录 1.基本部署 2.修改密码 3.开启root可远程连接配置 1.基本部署 01》》先查看OS类型&#xff0c;如果是Ubuntu在往下边看 rootspray:/etc/mysql/mysql.conf.d# lsb_release -a LSB Version: core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch Distributor ID: …

12-1-CSS 常用样式属性

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 CSS 常用样式属性1 CSS 三角形2 CSS 用户界面样式2.1 什么是界面样式2.2 鼠标…

【Spring】AOP——使用@around实现面向切面的方法增强

工作业务中&#xff0c;有大量分布式加锁的重复代码&#xff0c;存在两个问题&#xff0c;一是代码重复率高&#xff0c;二是容易产生霰弹式修改&#xff0c;使用注解和AOP可以实现代码复用&#xff0c;简化分布式锁加锁和解锁流程。 around注解是AspectJ框架提供的&#xff0c…

RK3568测试

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

Prometheus+grafana环境搭建Docker服务(docker+二进制两种方式安装)(八)

由于所有组件写一篇幅过长&#xff0c;所以每个组件分一篇方便查看&#xff0c;前七篇链接如下 Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客 Prometheusgrafana环境搭建rabbitmq(docker二进制两种方式安装)(二)-CSDN博客 Prometheusgrafana环…

java数据结构与算法刷题-----LeetCode172. 阶乘后的零

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 数学&#xff1a;阶乘的10因子个数数学优化:思路转变为求5的倍数…

【蓝桥杯选拔赛真题57】C++字符串反转 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解

目录 C字符串反转 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、推荐资料 C字符串反转 第十四届蓝桥杯青少年创意编程大赛C选拔赛真题 一、题目要求 1、编程实现 给定一个只包含大写字母"M…

C++进阶--C++11(2)

C11第一篇 C11是C编程语言的一个版本&#xff0c;于2011年发布。C11引入了许多新特性&#xff0c;为C语言提供了更强大和更现代化的编程能力。 可变参数模板 在C11中&#xff0c;可变参数模板可以定义接受任意数量和类型参数的函数模板或类模板。它可以表示0到任意个数&…

蓝桥杯 第2155题质因数个数 C++ Java Python

题目 思路和解题方法 目标是计算给定数 n 的质因数个数。可以使用了试除法来找到 n 的所有质因数 读取输入的数 n。从 2 开始遍历到 sqrt(n)&#xff0c;对于每个数 i&#xff1a; 如果 n 能被 i 整除&#xff0c;则进行以下操作&#xff1a; 将 n 除以 i&#xff0c;直到 n 不…

leetcode.面试题 02.07. 链表相交

题目 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 思路 假a在链表A上移动,b在链表B上移动&#xff0c;a移动完在B上开始&…

鸿蒙Lottie动画-实现控制动画的播放、暂停、倍速播放、播放顺序

介绍 本示例展示了lottie对动画的操作功能。引入Lottie模块&#xff0c;实现控制动画的播放、暂停、倍速播放、播放顺序、播放到指定帧停止或从指定帧开始播放、侦听事件等功能&#xff0c;动画资源路径必须是json格式。 效果预览 使用说明&#xff1a; 进入页面默认开始201…

铸铁平台的平面度

铸铁平台的平面度是指平台的表面平整程度&#xff0c;即平台表面与其理论平面之间的最大偏差。平台的平面度通常使用国际标准符号GD&T中的平面度符号&#xff08;ⓨ&#xff09;表示&#xff0c;单位为毫米&#xff08;mm&#xff09;或微米&#xff08;μm&#xff09;。 …