javaEE初阶————多线程初阶(4)

8.1 单例模式

这又是什么新的神奇玩意呢,我们先不谈单例模式,先来谈谈设计模式,什么是设计模式呢,我们只需要用设计模式就好了,而大佬们考虑的就多了,这些设计模式就像棋谱,只要按照棋谱来下,你的水平就不会太差,设计模式就是大佬们给我们小卡拉米设计的计算机棋谱,而单例模式就是其中之一,还有很多的设计模式;

那么什么是单例模式呢?

单例模式就是保证某个类在程序中只存在一份示例

这是为什么,就像DataSource,我们用数据库的时候有一份就好了,假设这个类实例化一次的开销就是几百个g,这样的开销可承担不起;

1)饿汉模式  

饿汉模式,看名字就知道,很饥饿,程序一创建,就让他有;

我们怎么做到呢?

public class Single {private static Single instance = new Single();private Single(){}public static Single getInstance(){return instance;}
}

我们来分析一下,第一行是静态成员的初始化,是在类加载的阶段触发的,而类加载会在程序一启动触发,这就保证了饥饿,当开始就要有这个实例,第二行是构造方法,我们将构造方法设置为私有,我们就不能在其他地方创建实例了,都是私有的获取不到实例呀,我们在加一行获得instance的方法getInstance;

Single single = new Single();

这段代码实际上是爆红的,

我们无法自己去new一个新的实例

Single s1 = Single.getInstance();Single s2 = Single.getInstance();if(s1 == s2){System.out.println("s1 and s2 are the same instance");}else {System.out.println("s1 and s2 are different instances");}

我们可以获得在类加载阶段的实例,我们知道s1和s2都是引用,如果刚才的饿汉模式对的话,s1引用的地址和s2应该是一样的

 我们看看运行结果

这就完成啦,那么这个代码是不是线程安全的呢

这里的return只涉及到读操作,instance = new single()也是在类加载的时候就发生了,所以刚才写的这个恶汉模式的代码是线程安全的;

2)懒汉模式 单线程版

懒汉模式是什么呢,也是看名字,很懒所以拖拖拉拉的,我们让他干一个事情它可能拖到好久才干活,我们现实生活中当然是不好的,但是计算机领域,懒可是个很好的词,我们想一下看小说,可能这个小说有几百万字,你选择看这个小说它难道要把数据库中所有的文章都加载给你吗,它可能只会加载一下部分,其余的都在数据库中躺着呢,这就是我们所谓的懒汉,但是这样给我们节省了很多很多的开销;

我们来实现一个;

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

再来分析一下,当静态成员初始化为null,构造方法仍然设置为私有化,我们只有在使用instance的时候再创建实例,这样就达成了懒汉;

直接创建还是不合法的,

Lazy s1 = Lazy.getInstance();Lazy s2 = Lazy.getInstance();if(s1 == s2){System.out.println("s1, and s2 are the same instance");}}

看着是不是相同的地址

 

还是哒,那么懒汉模式也完成了,这个是线程安全的吗?既然有标题3了,那么肯定是不安全的了,为啥呢,因为if()那边

这整块都会受到多线程的的影响;你想t1线程时instance==null,t1刚刚判断完毕,t2线程开始判断instance==null,那么t1实例化了一次对象,t2又实例化了一个对象,虽然会覆盖掉,垃圾回收机制也会处理,但是这个实例要是很大呢比如100G,又比如很多线程挤在这块new 对象,那么就会有很多很多的开销浪费,所以这个代码是线程不安全的,我妈接下来实现线程安全的;

3)懒汉模式 多线程版

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

这样就是线程安全版本了,还是举例嗷,t1线程加锁,判断为空,实例对象,解锁,这整个过程t2都是没机会进来的,t1返回instance实例,但是这个代码发现没有他是一直阻塞的,t1到这执行,其他都在阻塞等待,那么他们不是null的话直接结束就好了呀,我们可以再改进一下,

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

这样就保证了不为null的时候不会在这里阻塞等待; 

可能有同学会问这俩if看着好怪,单线程编程中是多余的,多线程每行都有它的意义;

4)懒汉模式 多线程进阶版

不是,刚才不都进阶完了吗,哈哈哈,其实代码还有个问题,就是指令重排序,

导致原因还是编译器优化,编译器可能会修改指令的执行顺序来提升性能,比如我们去菜市场买菜,沿着一条线来挑选食材肯定是很高效的,但是我们去各个区域乱找我们要买的食材肯定是速度慢一些的,指令重排序就是这样,我们在instance = new Lazy2()中就会发生这样的情况,

本来是 1,申请内存空间     2, 在空间上构造对象(初始化)   3, 把内存空间的首地址赋值给引用变量      这几步操作,但是由于指令重排序可能会发生  1 ->    3   ->   2  ->   这样的操作顺序,会导致什么结果呢,我们给引用传了一个没有初始化的对象,这个引用拿着一个啥也干不了的东西到处调用;我们怎么避免指令重排序呢,我们之前讲的内存可见性,我们使用volatile可以避免内存可见性,我们使用volatile也可以避免指令重排序的;

这样就完全没有线程安全问题了

———————————————————————————————————————————

8.2 阻塞队列

1)阻塞队列是什么

我们之前学数据结构的时候学过队列,队列是先进先出的特性,那么阻塞队列是什么呢,我们说过,不管是队列还是优先级队列都是线程不安全的,而阻塞队列是线程安全的队列,它支持多个线程往队列中放东西,阻塞队列会在队列满的时候阻塞其他线程往队列中放东西,并且在线程为空的时候阻止其他线程往队列中拿东西,就能保证线程安全;

2)生产消费者模型

阻塞队列典型的场景就是生产消费者模型,这个是啥呢:

我们举一个例子,大家有没有吃过自助水饺,有两个姐姐一直在那包饺子,她们就是生产者,我们来吃自助的就是消费者,她们把饺子包完之后就会放到冰柜中,我们去冰柜去取,而这个冰柜就是阻塞队列,那么我们不能不要这个阻塞队列吗,直接生产者与消费者交互不是更快更高效吗,确实是这样的,但是阻塞队列有两个优点:

1,解耦合

耦合这个名词大家不陌生吧,代码的耦合度就是代码的关联性,关联性越高越复杂就不好,为啥呢,比如我们要修改生产者的代码,却有好多代码与消费者有关联,我们就得大修改,然而加一个中间,就好很多了;

2,削峰填谷

这个是啥意思呢,我们来说说全世界最牛皮的系统,12306,它牛不牛,在每到过年的时候,几亿人都要来抢票,这么大的数据访问量,根本不会有几个系统能抗住这样的冲击,生产者与消费者直接交互肯定是不行的,这样阻塞队列就能起到一个缓冲的作用,虽然数据很多,但是我先把它存到队列中,让消费者慢慢来,在生产者所剩无几的时候,在把队列给消费者,让系统始终保持着一个平缓的速度;

当然啊,凡是必有代价,阻塞队列的出现意味着我们需要更复杂的结构和效率的降低的;

3)标准库当中的阻塞队列

java标准库中也提供了阻塞队列可以直接拿来用

BlockingQueue是一个接口;

提供了很多,我们使用阻塞队列的时候,有两个常用的方法,

.put()入队列 

.take()出队列

当然offer啥的也有单只有put和take是线程安全的;

我们使用阻塞队列的时候建议给定一个容量,要不默认的太大了

我们现在就来模拟一个生产消费者模型

public class Demo1 {public static void main(String[] args) {BlockingQueue<Integer> bq = new ArrayBlockingQueue<>(10);Thread t1 = new Thread(()->{Integer i = 0;for(;i<100;i++){System.out.println("生产者生产了"+i);try {bq.put(i);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"生产者");Thread t2 = new Thread(()->{Integer j = 0;for(;j<100;j++){try {Integer value = bq.take();System.out.println("消费者消费了"+ value);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"消费者");t1.start();t2.start();}
}

  来讲解下,我们在t1创建一个变量,我们循环生产元素放到阻塞队列中,t2线程也循环100次,取出队列中的元素;看看打印结果:

很长一溜,看着不明显,我们可以用sleep:

public class Demo1 {public static void main(String[] args) {BlockingQueue<Integer> bq = new ArrayBlockingQueue<>(10);Thread t1 = new Thread(()->{Integer i = 0;for(;i<100;i++){try {System.out.println("生产者生产了"+i);bq.put(i);Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"生产者");Thread t2 = new Thread(()->{Integer j = 0;for(;j<100;j++){try {Integer value = bq.take();System.out.println("消费者消费了"+ value);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"消费者");t1.start();t2.start();}
}

这样就很明显了,生产一个消费一个;

4)阻塞队列的模拟实现

哎,到这了大家有没有想过一个问题,博主好像讲的所有东西,什么链表,优先级队列,甚至二叉树啥的都要自己模拟实现一遍,这是为啥呀,加深理解是一方面,我们后期工作的时候,可能有很多魔改的东西,比如我们为了公司要去弄一个特定的阻塞队列,你要只会用连咋实现的都不知道那不炸了吗:

开始嗷

我们先想想,我们之前是怎么模拟实现队列的呢,先进先出,反正我想到的是循环队列,之前我发过练习题,有一个就是循环队列,不知道也没关系,我们再来梳理一遍;

两个指针,一个头指针,一个尾指针;

我们要实现两个操作,一个入队列put,一个出队列take;

先说入队列的注意事项,入队列要tail指针移动,而head不动,我们要判断满的时候,满了就不能放了而且要阻塞,并且下次进入要到head下标而不是让下标一直增加,比如到8下标下次就0而不是9,

出队列注意事项,要判空,当队列为空,我们就不能出队列了,要阻塞,出队列是对head操作这样我们就满足了先进先出的特性;

我们来实现代码

public class MyBlockingQueue {private int[] arr = null;private int head = 0;private int tail = 0;private int size = 0;public MyBlockingQueue(int capacity){arr = new int[capacity];}public void put(int in) throws InterruptedException {synchronized (this){while(size>=arr.length){this.wait();}arr[tail++] = in;if(tail >= arr.length){tail = 0;}size++;this.notify();}}public int take() throws InterruptedException {synchronized (this){while(size==0){this.wait();}int out = arr[head++];if(head >= arr.length){head = 0;}size--;this.notify();return out;}}}

public class Demo2 {public static void main(String[] args) {MyBlockingQueue myBlockingQueue = new MyBlockingQueue(8);Thread t1 = new Thread(()->{int i = 0;for(;i<100;i++){try {System.out.println("生产者生产"+i);myBlockingQueue.put(i);Thread.sleep(221);} catch (InterruptedException e) {throw new RuntimeException(e);}}},"生产者");Thread t2 = new Thread(()->{int j = 0;for (j = 0;j<100;j++){try {int value = myBlockingQueue.take();System.out.println("消费者消费"+ value);} catch (InterruptedException e) {throw new RuntimeException(e);}}}, "消费者");t1.start();t2.start();}
}

 

这就完成了,但是大家有没有疑问,

存在put中的wait被其他线程put的notify唤醒吗,而不是take中的notify

我们这个代码中在wait那使用了while循环来判断,即使生产者被生产者唤醒也会再次判断是否为满,重新阻塞,消费者那里也是一样;

———————————————————————————————————————————

8.3 线程池

1)线程池是什么

这个是什么,我们之前学javase的时候学过一个字符串常量池,java程序构建的时候常量就加载到了内存中了,线程池也是一样,将线程提前准备好,为啥要用线程池呢,我们最初使用进程来实现并发执行的效果,频繁创建销毁进程造成的开销很大,所以我们引入线程,但是随着计算机的发展,频繁销毁线程我们也觉得不够用了,我们使用线程池就能减少每次使用,创建销毁的开销了,

这里更主要的原因是创建销毁线程太慢了,而线程池能更高效的提升创建销毁线程的速度,;

不知道大家理没理解,更简单一点,我们可以把线程池想象成一个数组,我们造好各种线程,把线程放到里面,等到有任务的时候丢给这个线程池,而不用去不断的创建和销毁线程了;

为什么有了线程池,就能更快的创建和销毁线程呢?

普及一个小知识嗷:

操作系统 = 内核 + 配套的应用程序,   而这个内核它能管理硬件设备和给软件提供各种稳定的应用环境, 我们创建线程就要到内核中,这有一段开销,而我们使用线程池,它会省下跑到内核去的开销,线程池就能更快更高效,而且不去内核这段代码是可控的,去内核就涉及到了操作系统了,创建线程我们是不可控的;

2)标准库中的线程池

java标准库中也给我们提供了线程池,

ThreadPoolExector

我们可以通过submit(Runnable a)方法,把任务放到线程池中,让线程去执行任务,

那么线程如何初始化呢,我们去java帮助手册看一下

我嘞个豆,这么多参数

1)我们先看这个最多的,第一个参数corePoolSize 和第二个参数 maximumPoolSize是配套使用的,corePoolSize是核心线程数,我们可以理解为一个公司的员工数量,第二个参数是最大核心线程数,我们可以理解为固定员工加临时工,胡定员工干不过来了,临时员工就会变多,反之下降,CPU的默频和睿频也一样,默频固定速率,而睿频会随着工作压力的变高升高,但是他们都有升高上限 ;

2)然后我们看3和4,keepAliveTime 和 unit,第一个是长整形,是允许线程空闲的最大时间数,我们可以理解为员工的最大摸鱼时间,你天天摸鱼就给你开了,第二个参数unit是枚举类型,各个枚举对应着它的时间单位;

3)第五个参数,workQueue,我们看到他的参数是BlockingQueue,也就是阻塞队列,其实我们这个线程池就是一个生成消费者模型,池中的各种线程就是消费者,而submit带来的任务就是充当生产者,中间资源存放的地方就是工作队列;

4)第六个参数,threadFactory,线程工程,创建线程和线程的功能,我们还记得设计模式吗,就是程序员棋谱,第二个设计模式来了,工厂模式:

工厂模式有啥用呢,它弥补了构造方法的缺陷,它将对象的创建封装到工厂中,客户端通过工厂来new对象,降低了代码的耦合度,工厂模式有三种

1,简单工厂模式

这种工程模式的核心思想是用静态方法把new对象的过程封装起来,我们用代码来举例子:

我们先创建产品,弄一个产品接口,实现两个产品类,让产品都能使用use方法

public interface Product {public void use();
}
public class ProductA implements Product{@Overridepublic void use() {System.out.println("产品A");}
}
public class ProductB implements Product{@Overridepublic void use() {System.out.println("产品B");}
}

这样产品就弄好了我们来实现工厂,简单工厂模式的工厂是利用静态方法来根据不同条件构造对象,

public class Factory {public static Product create(String a){if(a.equals("A")){return new ProductA();} else if (a.equals("B")) {return new ProductB();}else {System.out.println("工厂没这产品");throw new IllegalArgumentException("没有这个类型");}}
}

 我们再在主方法中测试一下

ok的,另外说一下这个异常,IllegalArgumentException是运行时异常,通常表示传递的参数是不合法的;

这就是第一种简单工厂模式

2,工厂方法模式

 工厂方法模式的核心是把工厂变为父类,制造更多子类,每个子类负责一个产品,我们来试试:

产品的代码都不变

public interface Product {public void use();
}public class ProductA implements Product{@Overridepublic void use() {System.out.println("A产品");}
}public class ProductB implements Product{@Overridepublic void use() {System.out.println("B产品");}
}

 工厂接口

public interface Factory {Product create();
}

制造不同产品的不同工厂

public class FactoryA implements Factory{@Overridepublic Product create() {return new ProductA();}
}public class FactoryB implements Factory{@Overridepublic Product create() {return new ProductB();}
}

 最后主方法测试

public class Demo2 {public static void main(String[] args) {Factory factoryA = new FactoryA();Product productA = factoryA.create();productA.use();Factory factoryB = new FactoryB();Product productB = factoryB.create();productB.use();}
}

看看运行结果

 

第三种是抽象方法工厂模式

这个和第二种差不多只不过应用场景不同,第三种就是创建了多种产品,让一个工厂管理多个有相似性的产品,先不介绍了嗷,

5)最后一个参数是handler,就是拒绝策略,我们submit任务到阻塞队列的时候,当队列满了,我就会执行拒绝策略的内容,

有4种拒绝策略:

第一个AbortPolicy是直接抛异常,新的任务干不了,之前的也崩了,直接掀桌;

第二个CallerRunsPolicy是让submit的线程自行调用,啥意思呢,我们是线程池,我们已经忙不过来了,submit过来说你给我取个外卖,那我能干吗,我告诉他你自己去,submit低着头自己去外卖了;

第三个DiscardOldestPolicy抛弃线程中最老的任务,这个没啥好解释的,放弃最老的任务;

第四个DiscardPolicy丢弃最新的任务,也就是刚刚submit给的任务;

大家觉得怎么样,我们唠了这么多,我们只是在讨论ThreadPoolExector构造方法的参数,很麻烦对吧,但是java中是提供了直接用的线程池的,

newFixedThreadPool:创建固定线程数目的线程池

newCachedThreadPool:创建线程数目动态增长的线程池

还有一些,但是我们还是推荐自己写,不用官方的,这么多参数,用官方提供的封装方法是不可控的,还是推荐自己实现;

我们下期来自己实现线程池和定时器,java的多线程初阶我们就学完了

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

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

相关文章

能源物联网数据采集网关 多协议对接解决方案

安科瑞刘鸿鹏 摘要 随着配电系统智能化需求的提升&#xff0c;现代配电物联网&#xff08;IoT&#xff09;系统对数据采集、传输、处理及远程管理能力提出了更高要求。智能网关作为连接现场设备与上层管理平台的核心枢纽&#xff0c;其性能直接影响系统的实时性、可靠性与扩展…

Node.js 中的 Event 模块详解

Node.js 中的 Event 模块是实现事件驱动编程的核心模块。它基于观察者模式&#xff0c;允许对象&#xff08;称为“事件发射器”&#xff09;发布事件&#xff0c;而其他对象&#xff08;称为“事件监听器”&#xff09;可以订阅并响应这些事件。这种模式非常适合处理异步操作和…

Unity开发抖音小游戏播放视频

Unity开发抖音小游戏播放视频 介绍抖音小程序ios端视频无法播放RenderTexture问题总结 介绍 最近在做抖音小游戏播放视频&#xff0c;这里我使用的是Unity原生的VideoPlayer组件来播放视频&#xff0c;这里总结了一下我相关的报错以及能够正常播放视频的代码。如果还不知道怎么…

网络安全抑制 缓解 根除 恢复 网络安全如何解决

一、网络安全 网络是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然的或者恶意的原因而遭受到破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 二、如何防范网络安全问题 1、防范网络病毒。 2、配置防火墙。 3、采…

自有证书的rancher集群使用rke部署k8s集群异常

rancher使用自签域名&#xff0c;或者商业证书容易踩到的坑。 最开始的报错&#xff1a; docker logs kubelet‘s id E0214 13:04:14.590268 9614 pod_workers.go:1300] "Error syncing pod, skipping" err"failed to \"StartContainer\" for …

开源的轻量级分布式文件系统FastDFS

FastDFS 是一个开源的轻量级分布式文件系统&#xff0c;专为高性能的分布式文件存储设计&#xff0c;主要用于解决海量文件的存储、同步和访问问题。它特别适合以中小文件&#xff08;如图片、视频等&#xff09;为载体的在线服务&#xff0c;例如相册网站、视频网站等。 FastD…

Github 2025-02-12 C开源项目日报 Top7

根据Github Trendings的统计,今日(2025-02-12统计)共有7个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量C项目7Python项目2OpenSSL - 强大的开源加密工具包 创建周期:4012 天开发语言:C协议类型:Apache License 2.0Star数量:23449 个Fork数量:10…

深入浅出:Python 中的异步编程与协程

引言 大家好&#xff0c;今天我们来聊聊 异步编程 和 协程&#xff0c;这是近年来编程语言领域中的热点话题之一&#xff0c;尤其在 Python 中&#xff0c;它作为一种全新的编程模型&#xff0c;已经成为处理 IO密集型 任务的强力工具。尽管很多人对异步编程望而却步&#xff0…

高级 Conda 使用:环境导出、共享与优化

1. 引言 在 Conda 的基础包管理功能中&#xff0c;我们了解了如何安装、更新和卸载包。但对于开发者来说&#xff0c;如何更好地管理环境、导出环境配置、共享环境&#xff0c;以及如何优化 Conda 的使用效率&#xff0c;才是提高工作效率的关键。本篇博客将进一步深入 Conda …

三十一、micro-app踩坑

版本:0.8.6 1.子应用单独运行正常,基座加载子应用后接口404 原因:子应用请求的接口为相对地址,会以基座域名进行补全,导致报错。 解决方法:

MATLAB中isfield函数用法

目录 语法 说明 示例 确定输入名称是否为字段名称 isfield函数的功能是确定输入是否为结构体数组字段。 语法 TF isfield(S,field) 说明 如果 field 是结构体数组 S 的一个字段的名称&#xff0c;则 TF isfield(S,field) 返回 1。否则&#xff0c;将返回 0。 如果 fie…

在Autonomous DB中创建训练数据集

在Autonomous DB中创建训练数据集 概述背景步骤解析1. 定义公司术语表2. 使用SQL将数据转换为JSON格式3. 使用SPool命令将SQL查询结果输出为JSON文件4. 查看生成的JSON文件 结果示例结论 概述 在机器学习中&#xff0c;构建高质量的训练数据集是模型成功的关键&#xff0c;尤其…

ASP.NET Core 使用 FileStream 将 FileResult 文件发送到浏览器后删除该文件

FileStream 在向浏览器发送文件时节省了服务器内存和资源&#xff0c;但如果需要删除文件怎么办&#xff1f;本文介绍如何在发送文件后删除文件&#xff1b;用 C# 编写。 另请参阅&#xff1a;位图创建和下载 使用FileStream向浏览器发送数据效率更高&#xff0c;因为文件是从…

深入理解 Qt 信号与槽机制:原理、用法与优势

一、信号与槽的概念 在 Qt 编程中&#xff0c;信号与槽机制是实现对象间通信的核心工具。 信号&#xff1a;本质上是一种特殊的成员函数声明&#xff0c;它不包含函数体&#xff0c;仅用于通知其他对象某一事件的发生。例如&#xff0c;当用户点击界面上的按钮时&#xff0c;…

蓝桥杯(B组)-每日一题

题目&#xff1a; 思路&#xff1a; 首先将所有牛分类 1.a第一头母牛-每年年初生一头小母牛 2.不能生小牛的牛&#xff1a; b1-一岁小母牛 b2-二岁小母牛 b3-三岁小母牛 超过4岁就会再生一头小牛 因此计算每年生的小牛是第一头生的a再加上4岁后的生的 代码实现&#xff1…

处理项目中存在多个版本的jsqlparser依赖

异常提示 Correct the classpath of your application so that it contains a single, compatible version of net.sf.jsqlparser.statement.select.SelectExpressionIte实际问题 原因&#xff1a;项目中同时使用了 mybatis-plus 和 pagehelper&#xff0c;两者都用到了 jsqlpa…

Spring Boot 常用依赖详解:如何选择和使用常用依赖

在Spring Boot项目中&#xff0c;依赖&#xff08;Dependencies&#xff09;是项目的核心组成部分。每个依赖都提供了一些特定的功能或工具&#xff0c;帮助我们快速开发应用程序。本文将详细介绍Spring Boot中常用的依赖及其作用&#xff0c;并指导你如何根据项目需求选择合适…

模糊综合评价法:原理、步骤与MATLAB实现

引言 在复杂决策场景中&#xff0c;评价对象往往涉及多个相互关联的模糊因素。模糊综合评价法通过建立模糊关系矩阵&#xff0c;结合权重分配与合成算子&#xff0c;实现对多因素系统的科学评价。本文详细讲解模糊综合评价法的数学原理、操作步骤&#xff0c;并辅以MATLAB代码…

什么是偏光环形光源

偏光环形光源是一种特殊的光源&#xff0c;常用于机器视觉、光学检测和工业自动化等领域。它结合了环形光源和偏光技术&#xff0c;能够有效减少反射、增强对比度&#xff0c;特别适用于检测高反光或表面复杂的物体。 主要特点&#xff1a; 环形设计&#xff1a;光线均匀照射物…

组合的输出(信息学奥赛一本通-1317)

【题目描述】 排列与组合是常用的数学方法&#xff0c;其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n)&#xff0c;我们可以简单地将n个元素理解为自然数1&#xff0c;2&#xff0c;…&#xff0c;n&#xff0c;从中任取r个数。现要求你用递归的方法输出所有组合。 例如n…