Java基础学习笔记(二十)—— 多线程(2)

news/2024/5/20 21:07:38/文章来源:https://blog.csdn.net/hu_wei123/article/details/128820461

多线程

  • 1 线程池
    • 1.1 线程状态
    • 1.2 线程池基本原理
    • 1.3 创建线程池
    • 1.4 任务拒绝策略
  • 2 共享变量的问题与解决
    • 2.1 存在的问题
    • 2.2 Volatile解决
    • 2.3 synchronized解决
  • 3 原子性
    • 3.1 原子性的实现(synchronized)
    • 3.2 AtomicInteger
    • 3.3 悲观锁和乐观锁
  • 4 并发工具类
    • 4.1 Hashtable
    • 4.2 ConcurrentHashMap
    • 4.3 CountDownLatch
    • 4.4 Semaphore

1 线程池

1.1 线程状态

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。

状态被定义在了 java.lang.Thread.State 枚举类中,State枚举类的源码如下:

public class Thread {public enum State {/* 新建 */NEW , /* 可运行状态/就绪状态 */RUNNABLE , /* 阻塞状态 */BLOCKED , /* 无限等待状态 */WAITING , /* 计时等待 */TIMED_WAITING , /* 终止 */TERMINATED;}// 获取当前线程的状态public State getState() {return jdk.internal.misc.VM.toThreadState(threadStatus);}
}

通过源码我们可以看到 Java 中的线程存在 6 种状态,每种线程状态的含义如下:

在这里插入图片描述

各个状态的转换,如下图所示:
在这里插入图片描述

注意:

  • 线程处于运行状态的时候与系统里的CPU产生了关系,虚拟机是没有定义运行状态的

1.2 线程池基本原理

在这里插入图片描述

从上图可以看到,以前写多线程的弊端:用到线程的时候就创建,用完线程就消失

线程池可以看做成一个池子,在该池子中存储很多个线程。

线程池存在的意义:

​系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗。

针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

1.3 创建线程池

JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。

我们可以使用 Executors中 所提供的 静态 方法来创建线程池

  • static ExecutorService newCachedThreadPool() :创建一个默认的线程池
  • static newFixedThreadPool(int nThreads):创建一个指定最多线程数量的线程池
  1. 创建默认的线程池
public static void main(String[] args) throws InterruptedException {//创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.ExecutorService executorService = Executors.newCachedThreadPool();//Executors --- 可以帮助我们创建线程池对象//ExecutorService --- 可以帮助我们控制线程池executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});// 若不加以下代码,线程1还没来得及归还到线程池就要创建另一个线程,线程2// 若加上以下代码,线程1执行完成后(在线程睡眠过程中)归还到线程池,睡眠结束后,发现线程池中还有刚刚归还的线程1,再次创建线程时,不需要创建新的线程只需要拿出线程池中的线程1即可//Thread.sleep(2000);executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});executorService.shutdown();
}
  1. 创建一个指定最多线程数量的线程池
public static void main(String[] args) {//参数不是初始值而是最大值ExecutorService executorService = Executors.newFixedThreadPool(10);ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;System.out.println(pool.getPoolSize());//0executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});executorService.submit(()->{System.out.println(Thread.currentThread().getName() + "在执行了");});System.out.println(pool.getPoolSize());//2
//        executorService.shutdown();
}
  1. 自定义创建线程池对象

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)// 什么时候拒绝任务:当提交的任务>池子中最大线程数量+队列容量// 如何拒绝:/*  
corePoolSize:   核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime:  空闲线程最大存活时间,不能小于0
unit:           时间单位
workQueue:      任务队列,不能为null
threadFactory:  创建线程工厂,不能为null      
handler:        任务的拒绝策略,不能为null */ 

注意:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "在执行了");}
}
public class test {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.shutdown();}
}

1.4 任务拒绝策略

RejectedExecutionHandler 是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

  • ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常,是 默认的策略
  • ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
  • ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
  • ThreadPoolExecutor.CallerRunsPolicy: 调用任务的 run() 方法绕过线程池(交给主线程)直接执行。

2 共享变量的问题与解决

2.1 存在的问题

当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题。

总结:

  • 堆内存是唯一的,每一个线程都有自己的线程栈。
  • 每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
  • 在线程中,每一次使用是从变量的副本中获取的。

2.2 Volatile解决

Volatile关键字:强制线程每次在使用变量的时候,都会看一下共享区域最新的值

public class Money {public static volatile int money = 100000;// 结婚基金已经不是十万了//public static int money = 100000;//如果不加volatile关键字,首先执行线程1,会一直执行while循环,程序无法终止//线程1虽然知道基金是十万,但是当基金的余额发生变化的时候,无法知道最新的余额。
}
public class MyThread1 extends  Thread {@Overridepublic void run() {while(Money.money == 100000){//什么都不做}System.out.println("基金已经不是十万了");}
}
public class MyThread2 extends Thread {@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Money.money = 90000;}
}
public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}

2.3 synchronized解决

  • 线程获得锁
  • 清空变量副本
  • 拷贝共享变量最新的值到变量副本中
  • 执行代码
  • 将修改后变量副本中的值赋值给共享数据
  • 释放锁
public class Money {public static Object lock = new Object();public static int money = 100000;
}
public class MyThread1 extends  Thread {@Overridepublic void run() {while(true){synchronized (Money.lock){if(Money.money != 100000){System.out.println("结婚基金已经不是十万了");break;}}}}
}
public class MyThread2 extends Thread {@Overridepublic void run() {synchronized (Money.lock) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Money.money = 90000;}}
}
public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}

3 原子性

所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

3.1 原子性的实现(synchronized)

public class MyAtomThread implements Runnable {private int count = 0; //送冰淇淋的数量//private volatile int count = 0;//private Object lock = new Object();@Overridepublic void run() {for (int i = 0; i < 100; i++) {//count++;会做以下三件事://1,从共享数据中读取数据到本线程栈中.//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据.//synchronized(lock){count++;System.out.println("已经送了" + count + "个冰淇淋");// }}}
}
public class AtomDemo {public static void main(String[] args) {MyAtomThread atom = new MyAtomThread();for (int i = 0; i < 100; i++) {new Thread(atom).start();}}
}

注意:

  • 该程序最后的结果可能不会是“已经送了10000个冰淇淋”,那是因为 count++ 不是一个原子性操作,也就是说其在执行的过程中,有可能被其他线程打断操作
  • volatile 关键字只能保证每次在使用共享数据的时候是最新值,当其中一个线程改变了副本变量值还未写回共享区域的时候,其他线程获取的最新值还是未改变的,所以volatile关键字不能保证原子性
  • 使用synchronized代码块,创建锁对象可以保证原子性,将共享数据锁起来,一次只能有一个线程执行共享数据

3.2 AtomicInteger

Java 从 JDK1.5 开始提供了 java.util.concurrent.atomic 包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式

  1. 构造方法
  • public AtomicInteger():初始化一个默认值为0的原子型 Integer
  • public AtomicInteger(int initialValue):初始化一个指定值的原子型 Integer
  1. 常用方法
  • int get():获取值
  • int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。
  • int incrementAndGet():以原子方式将当前值加1,注意,这里返回的是自增后的值。
  • int addAndGet(int data):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
  • int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值。
import java.util.concurrent.atomic.AtomicInteger;public class test {public static void main(String[] args) {AtomicInteger ac1 = new AtomicInteger();AtomicInteger ac2 = new AtomicInteger(100);int i1 = ac1.get();int i2 = ac2.get();System.out.println(i1); //0System.out.println(i2); //100int andIncrement = ac2.getAndIncrement();System.out.println(andIncrement); //100System.out.println(ac2.get()); //101int incrementAndGet = ac2.incrementAndGet();System.out.println(incrementAndGet); //102System.out.println(ac2.get()); //102int addAndGet = ac2.addAndGet(30);System.out.println(addAndGet); //132System.out.println(ac2.get()); //132int andSet = ac2.getAndSet(10);System.out.println(andSet); //132System.out.println(ac2.get()); //10}
}
  1. AtomicInteger原理

继续 3.1 中的例子,使用AtomicInteger实现原子性

import java.util.concurrent.atomic.AtomicInteger;public class MyAtomThread implements Runnable {AtomicInteger ac = new AtomicInteger(0);@Overridepublic void run() {for (int i = 0; i < 100; i++) {int count = ac.incrementAndGet();//相当于把count++;的三步变成一个整体System.out.println("已经送了" + count + "个冰淇淋");}}
}
public class AtomDemo {public static void main(String[] args) {MyAtomThread atom = new MyAtomThread();for (int i = 0; i < 100; i++) {new Thread(atom).start();}}
}

AtomicInteger原理: 自旋锁 + CAS 算法

CAS算法:

  • 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
  • 当旧的预期值A == 内存值 此时修改成功,将V改为B
  • 当旧的预期值A!=内存值 此时修改失败,不做任何操作,并重新获取现在的最新值(这个重新获取的动作就是 自旋

CAS算法总结:在修改共享数据的时候,把原来的旧值记录下来了。

  • 如果现在内存中的值跟原来的旧值一样,证明没有其他线程操作过内存值,则修改成功。
  • 如果现在内存中的值跟原来的旧值不一样了,证明已经有其他线程操作过内存值了。
    则修改失败,需要获取现在最新的值,再次进行操作,这个重新获取就是自旋

3.3 悲观锁和乐观锁

synchronized 和 CAS 的区别 :

  • 相同点:在多线程情况下,都可以保证共享数据的安全性。
  • 不同点:
    • synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁
    • CAS是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。(乐观锁
      • 如果别人修改过,那么我再次获取现在最新的值。
      • 如果别人没有修改过,那么我现在直接修改共享数据的值

4 并发工具类

4.1 Hashtable

在集合类中 HashMap 是比较常用的集合对象,但是 HashMap 是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用 Hashtable,但是 Hashtable 的效率低下。

import java.util.HashMap;public class MyHashMapDemo {public static void main(String[] args) throws InterruptedException {//HashMap<String, String> hm = new HashMap<>();Hashtable<String, String> hm = new Hashtable<>();Thread t1 = new Thread(() -> {for (int i = 0; i < 25; i++) {hm.put(i + "", i + "");}});Thread t2 = new Thread(() -> {for (int i = 25; i < 51; i++) {hm.put(i + "", i + "");}});t1.start();t2.start();System.out.println("----------------------------");Thread.sleep(1000);//0-0 1-1 ..... 50- 50for (int i = 0; i < 51; i++) {System.out.println(hm.get(i + ""));}//0 1 2 3 .... 50}
}

总结:

  • Hashtable采取悲观锁 synchronized 的形式保证数据的安全性
  • 只要有线程访问,会将整张表全部锁起来,所以Hashtable的效率低下

4.2 ConcurrentHashMap

  • HashMap是线程不安全的。多线程环境下会有数据安全问题
  • Hashtable是线程安全的,但是会将整张表锁起来,效率低下
  • ConcurrentHashMap也是线程安全的,效率较高。(在JDK7和JDK8中,底层原理不一样)

Map接口体系结构:
在这里插入图片描述

  1. ConcurrentHashMap1.7原理

在这里插入图片描述

注意:

  • 当两个元素挂在同一个小数组的同一位置,旧元素会挂在新元素的后面
  • 小数组将要添加第二个索引位置的元素之前会扩容再添加
  1. ConcurrentHashMap1.8原理

底层结构:哈希表。( 数组、链表、红黑树的结合体)

结合CAS机制+ synchronized同步代码块形式保证线程安全。

在这里插入图片描述

总结 :

  • 如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。 在第一次添加元素的时候创建哈希表
  • 计算当前元素应存入的索引。
  • 如果该索引位置为null,则利用cas算法,将本结点添加到数组中。
  • 如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
  • 当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性

4.3 CountDownLatch

使用场景:让某一条线程等待其他线程执行完毕之后再执行

在这里插入图片描述

三个孩子线程

import java.util.concurrent.CountDownLatch;public class ChileThread1 extends Thread {private CountDownLatch countDownLatch;public ChileThread1(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.吃饺子for (int i = 1; i <= 10; i++) {System.out.println(getName() + "在吃第" + i + "个饺子");}//2.吃完说一声//每一次countDown方法的时候,就让计数器-1countDownLatch.countDown();}
}
import java.util.concurrent.CountDownLatch;public class ChileThread2 extends Thread {private CountDownLatch countDownLatch;public ChileThread2(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.吃饺子for (int i = 1; i <= 15; i++) {System.out.println(getName() + "在吃第" + i + "个饺子");}//2.吃完说一声//每一次countDown方法的时候,就让计数器-1countDownLatch.countDown();}
}
import java.util.concurrent.CountDownLatch;public class ChileThread3 extends Thread {private CountDownLatch countDownLatch;public ChileThread3(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.吃饺子for (int i = 1; i <= 20; i++) {System.out.println(getName() + "在吃第" + i + "个饺子");}//2.吃完说一声//每一次countDown方法的时候,就让计数器-1countDownLatch.countDown();}
}

一个妈妈线程

import java.util.concurrent.CountDownLatch;public class MotherThread extends Thread {private CountDownLatch countDownLatch;public MotherThread(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.等待try {//当计数器变成0的时候,会自动唤醒这里等待的线程。countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}//2.收拾碗筷System.out.println("妈妈在收拾碗筷");}
}

测试类

import java.util.concurrent.CountDownLatch;public class MyCountDownLatchDemo {public static void main(String[] args) {//1.创建CountDownLatch的对象,需要传递给四个线程。//在底层就定义了一个计数器,此时计数器的值就是3CountDownLatch countDownLatch = new CountDownLatch(3);//2.创建四个线程对象并开启他们。MotherThread motherThread = new MotherThread(countDownLatch);motherThread.start();ChileThread1 t1 = new ChileThread1(countDownLatch);t1.setName("小明");ChileThread2 t2 = new ChileThread2(countDownLatch);t2.setName("小红");ChileThread3 t3 = new ChileThread3(countDownLatch);t3.setName("小刚");t1.start();t2.start();t3.start();}
}

4.4 Semaphore

使用场景:可以控制访问特定资源的线程数量

import java.util.concurrent.Semaphore;public class MyRunnable implements Runnable {//1.获得管理员对象,允许几个线程同时运行private Semaphore semaphore = new Semaphore(2);@Overridepublic void run() {//2.获得通行证try {semaphore.acquire();//3.开始行驶System.out.println("获得了通行证开始行驶");Thread.sleep(2000);System.out.println("归还通行证");//4.归还通行证semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}
}
public class MySemaphoreDemo {public static void main(String[] args) {MyRunnable mr = new MyRunnable();for (int i = 0; i < 100; i++) {new Thread(mr).start();}}
}

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

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

相关文章

算法 | 动态规划 | 系列题目讲解(思路记录part.1)

文章目录字符串分割三角矩阵路径总数最小路径和字符串分割 问题描述 给定一个字符串和一个词典dict&#xff0c;确定s是否可以根据词典中的词分成 一个或多个单词。 比如&#xff0c;给定 s “leetcode” dict [“leet”, “code”] 返回true&#xff0c;因为"leetcode…

吾剑未尝不利,国内Azure平替,科大讯飞人工智能免费AI语音合成(TTS)服务Python3.10接入

微软Azure平台的语音合成(TTS)技术确实神乎其技&#xff0c;这一点在之前的一篇&#xff1a;含辞未吐,声若幽兰,史上最强免费人工智能AI语音合成TTS服务微软Azure(Python3.10接入)&#xff0c;已经做过详细介绍&#xff0c;然则Azure平台需要信用卡验证&#xff0c;有一定门槛&…

基于Apache Maven构建多模块项目

title: 基于Apache Maven构建多模块项目 date: 2022-04-10 00:00:00 tags: Apache Maven多模块 categories:Maven 介绍 多模块项目由管理一组子模块的聚合器 POM 来构建。在大多数情况下聚合器位于项目的根目录中&#xff0c;并且必须是 pom 类型的项目。子模块是常规的 Mave…

CNN网络:ResNet(四)

ResNet论文[https://arxiv.org/pdf/1512.03385.pdf]。RestNet网络结构ResNet在2015年被提出&#xff0c;在ImageNet比赛classification任务上获得第一名&#xff0c;因为它“简单与实用”并存&#xff0c;之后很多方法都建立在ResNet50或者ResNet101的基础上完成的&#xff0c;…

手机逻辑系统(2)---逻 辑 音 频 电 路

第二节 逻 辑 音 频 电 路 逻辑音频电路在手机电路中占有重要的地位,它是手机系统的心脏。 逻辑音频电路包含无线通信呼叫处理、音频处理、数字语音处理、射频逻辑接口电路、各种射频功能控制、电源管理和用户接口模组等。 任何一部手机的逻辑音频电路部分都包含以上的一些功能…

1.2.4存储结构-磁盘管理:磁盘优化分布存储、磁盘优化分布存储例题

1.2.4存储结构-磁盘管理&#xff1a;磁盘优化分布存储、磁盘优化分布存储例题磁盘优化分布存储磁盘优化分布存储 假设某磁盘的每个磁道划分成11个物理块&#xff0c;每块存放1个逻辑记录。逻辑记录R0&#xff0c;R1&#xff0c;…&#xff0c;R9&#xff0c;R10存放在同一个磁…

将U盘制作为启动盘

将U盘制作为启动盘 制作之前需要先保证U盘中没有重要的文件&#xff0c;因为制作时会将已有文件删除 1 安装制作软件【wePE】 ①官网选择对应PE版本下载安装 官网下载地址:http://www.wepe.com.cn IT天空的U盘装机助理&#xff1a;https://www.itiankong.net/thread-357573-1…

Ubuntu18 安装python3.7及多版本切换

1.安装3.7添加源sudo add-apt-repository ppa:deadsnakes/ppa检查更新sudo apt-get update 安装python3.7sudo apt-get install python3.72.使用 update-alternatives 来为整个系统更改Python 版本查看python替代版本信息~$ update-alternatives --display python但是结果为upd…

数字化发展趋势:打破企业边界,实现产业互联

据中欧商业在线发布的《2022未来管理人才白皮书》显示&#xff0c;在参加调研的1000家企业中&#xff0c;有77%的企业已经在公司业务中运用了数字化技术&#xff1b;更有7%的企业表示将在更深层面推进数字化转型工作。 当企业在业务层面完成数字化转型&#xff0c;下一步会走向…

知识图谱简介

知识图谱简介 知识图谱&#xff0c;是结构化的语义知识库&#xff0c;用于迅速描述物理世界中的概念及其相互关系&#xff0c;通过知识图谱能够将Web上的信息**、数据以及链接关系聚集为知识&#xff0c;使信息资源更易于计算、理解以及评价&#xff0c;并能实现知识的快速响应…

Keil中代码的颜色设置 ( 很 全 )[通俗易懂](转载)

https://cloud.tencent.com/developer/article/2081534Keil中代码的颜色设置 ( 很 全 )[通俗易懂]发布于2022-08-25 12:26:13阅读 1.8K0大家好&#xff0c;又见面了&#xff0c;我是你们的朋友全栈君。因为长时间要编程&#xff0c;对于keil上的黑字白底&#xff0c;如果看久了…

Python实现的通讯录

"为何表情&#xff0c;要让这世界安排&#xff1f;"诶&#xff0c;我们也对python的一些基础语法有了一定能的了解了。并且在这基础上&#xff0c;学习了python中的文件操作&#xff0c;那么有了这些东西以后啊&#xff0c;我们能做什么呢&#xff1f;或许对很多数据…

揭秘PPTC(自恢复保险丝)的四大使用原则

PPTC自恢复保险丝有贴片式以及插件式两种&#xff0c;封装形式多样&#xff0c;型号齐全&#xff0c;那么&#xff0c;在使用过程中&#xff0c;应该要注意什么&#xff1f;你知道吗&#xff1f;接下来&#xff0c;优恩小编将为你揭秘PPTC(自恢复保险丝)的四大使用原则。一、规…

Spring boot项目开发实战一(环境搭建)

技术栈选型 最近在实习好久没时间做过项目了&#xff0c;本次将借用公司的技术完成一个基于spring boot的实战项目&#xff0c;同时也巩固spring的相关知识。项目大体是一个后台管理系统&#xff0c;没有前台&#xff0c;用于数据分析和可视化。如下是初步的可视化界面&#x…

MySQL8.0 集群搭建

文章目录环境准备安装 MySQL 8.0配置主服务配置从服务器主从复制&#xff1a;即主服务器上的所有操作&#xff08;创建库&#xff0c;修改表等&#xff09;会被同步到从服务器上&#xff0c;但是在从服务器上的操作不会进入到主服务器中 环境准备 两台服务器&#xff0c;一主…

【Classical Network】Xception

文章目录深度可分离卷积Inception发展GoogleNetInception Networkinception V1inception V2inception V3inception V4Xception参考文章 经典卷积架构的PyTorch实现&#xff1a;Xception 参考文章 卷积神经网络结构简述&#xff08;二&#xff09;Inception系列网络 github 项目…

Springboot扩展点之InstantiationAwareBeanPostProcessor

前言前面介绍了Springboot的扩展点之BeanPostProcessor&#xff0c;再来介绍另一个扩展点InstantiationAwareBeanPostProcessor就容易多了。因为InstantiationAwareBeanPostProcessor也属于Bean级的后置处理器&#xff0c;还继于BeanPostProcessor&#xff0c;因此Instantiatio…

【Spring Cloud Alibaba】(二)微服务调用组件Feign原理+实战

系列目录 【Spring Cloud Alibaba】&#xff08;一&#xff09;微服务介绍 及 Nacos注册中心实战 本文目录系列目录前言什么是RPC&#xff1f;Feign和OpenFeign都是什么&#xff1f;HTTP调用 vs Feign(RPC)调用单独使用Feign实战Feign核心源码解读Feign整体设计架构Spring Clo…

PyQt5学习 阶段一

前言&#xff1a;PyQt5介绍PyQt是基于Digia公司强大的图形程序框架Qt的Python接口&#xff0c;由一组Python模块构成&#xff0c;它是一个创建GUI应用程序的工具包&#xff0c;由Phil Thompson开发。PyQt5的基本类&#xff1a;官方提供的帮助网址&#xff1a;https://www.river…

每天10个前端小知识 【Day 8】

前端面试基础知识题 1. Javascript中如何实现函数缓存&#xff1f;函数缓存有哪些应用场景&#xff1f; 函数缓存&#xff0c;就是将函数运算过的结果进行缓存。本质上就是用空间&#xff08;缓存存储&#xff09;换时间&#xff08;计算过程&#xff09;&#xff0c; 常用于…