JavaSE多线程线程池

文章目录

  • 1. 多线程入门
    • 1.1 多线程相关概念
    • 1.2 什么是多线程
    • 1.3 多线程的创建方式
      • 1.3.1 继承 Thread 的方式
      • 1.3.2 实现 Runnable 接口的方式
      • 1.3.3 实现 Callable 接口的方式
      • 1.3.4 Thread 类中常用方法
      • 1.3.5 sleep() 方法 和 wait() 方法区别:
  • 2. 线程安全
    • 2.1 线程安全产生的原因
    • 2.2 线程的同步
    • 2.3 同步代码块
    • 2.4 同步方法
    • 2.5 Lock 锁
  • 3. 线程死锁
  • 4. 线程的状态
  • 5. 线程池
    • 5.1 线程使用存在问题
    • 5.2 线程池介绍
    • 5.3 线程池使用的大致流程
    • 5.4 线程池的好处
    • 5.5 Java 提供的线程池
    • 5.6 线程池处理 Runable 任务
    • 5.7 线程池处理 Callable 任务
  • 6. 自定义线程池
  • 7. volatile
      • 如何保证变量的可见性?
      • 如何禁止指令重排序?
      • volatile 可以保证原子性么?
  • 8. AtomicInteger
    • 8.1 AtomicInteger-内存解析
    • 8.2 悲观锁和乐观锁
  • 9. 并发工具类
    • 9.1 Hashtable
    • 9.2 ConcurrentHashMap
      • ConcurrentHashMap1.7原理
      • ConcurrentHashMap1.8原理


在这里插入图片描述

1. 多线程入门

1.1 多线程相关概念

  • 并发与并行
    • 并行:在同一时刻,有多个任务在多个CPU上同时执行。
    • 并发:在同一时刻,有多个任务在单个CPU上交替执行。
  • 进程与线程
    • 进程:就是操作系统中正在运行的一个应用程序。
    • 线程:就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

1.2 什么是多线程

  • 是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
  • 好处 : 提高任务的执行性能。

1.3 多线程的创建方式

1.3.1 继承 Thread 的方式

//	基本步骤 :
//	1 创建一个类继承Thread类。
//	2 在类中重写run方法(线程执行的任务放在这里)
//	3 创建线程对象,调用线程的start方法开启线程。
public class MyThread01 {public static void main(String[] args) {// 创建线程对象,调用线程的start方法开启线程。MyThread mt = new MyThread();mt.start();// main方法中的任务for (int i = 1; i <= 100; i++) {System.out.println("i:" + i);}}
}// 创建一个类继承Thread类。
class MyThread extends Thread {// 在类中重写run方法(线程执行的任务放在这里)@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("i:" + i);}}
}

1.3.2 实现 Runnable 接口的方式

//	基本步骤 :
//	1 定义任务类实现Runnable,并重写run方法
//	2 创建任务对象
//	3 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
//	4 调用线程的start方法,开启线程
public class MyThread02 {public static void main(String[] args) {// 创建线程对象,调用线程的start方法开启线程。MyRunnable mr = new MyRunnable();Thread thread= new Thread(mr);thread.start();// main方法中的任务for (int i = 1; i <= 100; i++) {System.out.println("i:" + i);}}}// 1 定义任务类实现Runnable,并重写run方法
class MyRunnable implements Runnable {// 在类中重写run方法(线程执行的任务放在这里)@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("i:" + i);}}
}

1.3.3 实现 Callable 接口的方式

public class Thread3 {public static void main(String[] args) throws ExecutionException, InterruptedException {ThreadThree threadThree = new ThreadThree();FutureTask task = new FutureTask(threadThree);Thread thread = new Thread(task);thread.start();//System.out.println(task.get());for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " : " + i);}}
}class ThreadThree implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " : " + i);}return "end ";}
}

1.3.4 Thread 类中常用方法

  • String getName():返回此线程的名称
  • Thread类中设置线程的名字
    • void setName(String name):将此线程的名称更改为等于参数 name
    • 通过构造方法也可以设置线程名称
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用
  • public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
  • 线程有两种调度模型
    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

1.3.5 sleep() 方法 和 wait() 方法区别:

sleep方法是Thread类的静态方法,wait()是Object超类的成员方法
调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁。sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。在调用sleep()方法的过程中,线程不会释放对象锁。
因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
而当调用wait()方法的时候,线程会放弃对象锁,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。
sleep方法需要抛异常,wait方法不需要
sleep方法可以在任何地方使用,wait方法只能在同步方法和同步代码块中使用

2. 线程安全

2.1 线程安全产生的原因

多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了
举例:略

问题出现的原因 : 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

2.2 线程的同步

  • 概述 : java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性

  • 分类

    • 同步代码块
    • 同步方法
    • 锁机制,Lock

2.3 同步代码块

同步代码块 : 锁住多条语句操作共享数据,可以使用同步代码块实现
第一部分 : 格式

synchronized(任意对象) {
多条语句操作共享数据的代码

}
第二部分 : 注意
1 默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
2 当线程执行完出来了,锁才会自动打开
第三部分 : 同步的好处和弊端

好处 : 解决了多线程的数据安全问题
弊端 :
当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

注意:当该多线程类实现方式是继承Thread时,创建多个线程对象的时候,并且锁对象是 this 的时候 那么这个锁对象其实不是唯一的,会有问题滴。

public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票@Overridepublic void run() {while (true) {synchronized (Ticket.class) {// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {break;} else {// 模拟出票的时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");}}}}
}

2.4 同步方法

同步方法:就是把synchronized关键字加到方法上

格式:修饰符 synchronized 返回值类型 方法名(方法参数) { }

同步代码块和同步方法的区别:
1 同步代码块可以锁住指定代码, 同步方法是锁住方法中所有代码
2
同步代码块可以指定锁对象, 同步方法不能指定锁对象

注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
1 对于非 static 方法,
同步锁就是this。
2 对于 static 方法, 我们使用当前方法所在类的字节码对象(类名.class)。
Class类型的对象

/*同步方法:就是把synchronized关键字加到方法上格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }同步代码块和同步方法的区别:1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码2 同步代码块可以指定锁对象,同步方法不能指定锁对象注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。1 对于非static方法,同步锁就是this。2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象*/
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票@Overridepublic void run() {while (true) {if (method()) {break;}}}private synchronized boolean method() {// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {return true;} else {// 模拟出票的时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");return false;}}
}

2.5 Lock 锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,SO ,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock 中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁

Lock 是接口不能直接实例化,这里采用它的实现类 ReentrantLock 来实例化
ReentrantLock 的构造方法
ReentrantLock():创建一个 ReentrantLock 的实例

注意:多个线程使用相同的 Lock 锁对象,需要多线程操作数据的代码放在 lock() 和 unLock() 方法之间。一定要确保 unlock 最后能够调用

import java.util.concurrent.locks.ReentrantLock;/*虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象LockLock中提供了获得锁和释放锁的方法void lock():获得锁void unlock():释放锁Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法ReentrantLock():创建一个ReentrantLock的实例注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用*/
public class Ticket implements Runnable {private int ticketCount = 100; // 一共有一百张票private static ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {lock.lock();// 加锁// 如果票的数量为0 , 那么停止买票if (ticketCount <= 0) {break;} else {// 模拟出票的时间Thread.sleep(100);// 有剩余的票 , 开始卖票ticketCount--;System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();// 释放锁}}}
}

3. 线程死锁

概述 死锁是一种少见的,而且难于调试的错误,在两个线程对两个同步锁对象具有循环依赖时,就会大概率的出现死锁。我们要避免死锁的产生。否则一旦死锁,除了重启没有其他办法的.

产生条件

  • 多个线程
  • 存在锁对象的循环依赖

4. 线程的状态

线程的状态
线程的状态

线程的转换

线程通信
线程间的通讯技术就是通过等待和唤醒机制,来实现多个线程协同操作完成某一项任务,例如经典的生产者和消费者案例。等待唤醒机制其实就是让线程进入等待状态或者让线程从等待状态中唤醒,需要用到两种方法,如下:
等待方法

  • void wait() 让线程进入无限等待。
  • void wait(long timeout) 让线程进入计时等待
  • 以上两个方法调用会导致当前线程释放掉锁资源。

唤醒方法:

  • void notify() 唤醒在此对象监视器(锁对象)上等待的单个线程。
  • void notifyAll() 唤醒在此对象监视器上等待的所有线程。
  • 以上两个方法调用不会导致当前线程释放掉锁资源。

注意:

等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中调用)
等待和唤醒方法应该使用相同的锁对象调用

5. 线程池

5.1 线程使用存在问题

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
如果大量线程在执行,会涉及到线程间上下文的切换,会极大的消耗CPU运算资源。

5.2 线程池介绍

其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

5.3 线程池使用的大致流程

  • 创建线程池指定线程开启的数量
  • 提交任务给线程池,线程池中的线程就会获取任务,进行处理任务。
  • 线程处理完任务,不会销毁,而是返回到线程池中,等待下一个任务执行。
  • 如果线程池中的所有线程都被占用,提交的任务,只能等待线程池中的线程处理完当前任。

5.4 线程池的好处

  • 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 提高响应速度。当任务到达时,任务可以不需要等待线程创建 , 就能立即执行。
  • 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存 (每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

5.5 Java 提供的线程池

  • java.util.concurrent.ExecutorService 是线程池接口类型。使用时我们不需自己实现,JDK已经帮我们实现好了
  • 获取线程池我们使用工具类 java.util.concurrent.Executors的静态方
    • public static ExecutorService newFixedThreadPool (int num) : 指定线程池最大线程池数量获取线程池
  • 线程池ExecutorService的相关方法
    • Future submit(Callable task)
    • Future<?> submit(Runnable task)
  • 关闭线程池方法(一般不使用关闭方法,除非后期不用或者很长时间都不用,就可以关闭)
    • void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

5.6 线程池处理 Runable 任务

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*1 需求 :使用线程池模拟游泳教练教学生游泳。游泳馆(线程池)内有3名教练(线程)游泳馆招收了5名学员学习游泳(任务)。2 实现步骤:创建线程池指定3个线程定义学员类实现Runnable,创建学员对象给线程池*/
public class Test1 {public static void main(String[] args) {// 创建指定线程的线程池ExecutorService threadPool = Executors.newFixedThreadPool(3);// 提交任务threadPool.submit(new Student("小花"));threadPool.submit(new Student("小红"));threadPool.submit(new Student("小明"));//	threadPool.shutdown();// 关闭线程池}
}class Student implements Runnable {private String name;public Student(String name) {this.name = name;}@Overridepublic void run() {String coach = Thread.currentThread().getName();System.out.println(coach + "正在教" + name + "游泳...");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(coach + "教" + name + "游泳完毕.");}
}

5.7 线程池处理 Callable 任务

import java.util.concurrent.*;/*需求: Callable任务处理使用步骤1 创建线程池2 定义Callable任务3 创建Callable任务,提交任务给线程池4 获取执行结果<T> Future<T> submit(Callable<T> task) : 提交Callable任务方法    返回值类型Future的作用就是为了获取任务执行的结果。Future是一个接口,里面存在一个get方法用来获取值练一练:使用线程池计算 从0~n的和,并将结果返回*/
public class Test2 {public static void main(String[] args) throws ExecutionException, InterruptedException {// 创建指定线程数量的线程池ExecutorService threadPool = Executors.newFixedThreadPool(10);Future<Integer> future = threadPool.submit(new CalculateTask(100));Integer sum = future.get();System.out.println(sum);}
}// 使用线程池计算 从0~n的和,并将结果返回
class CalculateTask implements Callable<Integer> {private int num;public CalculateTask(int num) {this.num = num;}@Overridepublic Integer call() throws Exception {int sum = 0;// 求和变量for (int i = 0; i <= num; i++) {sum += i;}return sum;}
}

6. 自定义线程池

参数
参数应用
该拒绝策略 在 超出(最大线程+队列数)时报错如下:

拒绝策略异常
推荐的拒绝策略:ThreadPoolExecutor.CallerRunsPolicy

饱和策略

7. volatile

如何保证变量的可见性?

在 Java 中,volatile 关键字可以保证变量的可见性,如果我们将变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

volatile 关键字其实并非是 Java 语言特有的,在 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。如果我们将一个变量使用 volatile 修饰,这就指示 编译器,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。

如何禁止指令重排序?

在 Java 中,volatile 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。 如果我们将变量声明为 volatile ,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序。
在 Java 中,Unsafe 类提供了三个开箱即用的内存屏障相关的方法,屏蔽了操作系统底层的差异:

public native void loadFence();
public native void storeFence();
public native void fullFence();

理论上来说,你通过这个三个方法也可以实现和volatile禁止重排序一样的效果,只是会麻烦一些。

volatile 可以保证原子性么?

volatile 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。
很多人会误认为自增操作 inc++ 是原子性的,实际上,inc++ 其实是一个复合操作,包括三步:

  1. 读取 inc 的值。
  2. 对 inc 加 1。
  3. 将 inc 的值写回内存。

volatile 是无法保证这三个操作是具有原子性的,有可能导致下面这种情况出现:

  1. 线程 1 对 inc 进行读取操作之后,还未对其进行修改。线程 2 又读取了 inc的值并对其进行修改(+1),再将inc 的值写回内存。
  2. 线程 2 操作完毕后,线程 1 对 inc的值进行修改(+1),再将inc 的值写回内存。

这也就导致两个线程分别对 inc 进行了一次自增操作后,inc 实际上只增加了 1。
其实,如果想要保证上面的代码运行正确也非常简单,利用 synchronized 、Lock或者AtomicInteger都可以。

8. AtomicInteger

概述:java 从 JDK1.5 开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变
量的类型有很多种,所以在 Atomic 包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解
使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型

以上 3 个类提供的方法几乎一模一样,所以本节仅以 AtomicInteger 为例进行讲解,AtomicInteger 的常用方法如下:

//	初始化一个默认值为0的原子型Integer
public AtomicInteger()   			  //	 初始化一个指定值的原子型Integer
public AtomicInteger(int initialValue)//	获取值
int get()
//	 以原子方式将当前值加1,注意,这里返回的是自增前的值。   
int getAndIncrement()
//	以原子方式将当前值加1,注意,这里返回的是自增后的值。
int incrementAndGet()  
//	 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int addAndGet(int data)	
//	 以原子方式设置为newValue的值,并返回旧值。
int getAndSet(int value)

8.1 AtomicInteger-内存解析

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

8.2 悲观锁和乐观锁

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

9. 并发工具类

9.1 Hashtable

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

9.2 ConcurrentHashMap

ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。
基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。
总结 :

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

ConcurrentHashMap1.7原理

1.7

ConcurrentHashMap1.8原理

1.8

总结 :

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

参考:
java线程池ThreadPoolExecutor类使用详解 - DaFanJoy - 博客园

Java 并发常见面试题总结(下)



在这里插入图片描述



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

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

相关文章

项目实战--Spring Boot + Minio文件切片上传下载

1.搭建环境 引入项目依赖 <!-- 操作minio的java客户端--> <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version> </dependency> <!-- jwt鉴权相应依赖--> &…

Linux下编程之内存检查

前言 我们在进行编程时&#xff0c;有时不免会无意中写出一些容易导致内存问题&#xff08;可能一时表象上正常&#xff09;的代码&#xff0c;导致的后果肯定是不好的&#xff0c;就像一颗颗“哑弹”&#xff0c;令人心慌。网上推荐的辅助工具很多&#xff0c;此篇文章…

Unity 功能 之 创建 【Unity Package】 Manager 自己自定义管理的包的简单整理

Unity 功能 之 创建 【Unity Package】 Manager 自己自定义管理的包的简单整理 目录 Unity 功能 之 创建 【Unity Package】 Manager 自己自定义管理的包的简单整理 一、简单介绍 二、Unity Package 的目录结构 三、package.json 说明 四、程序集定义 1、程序集定义说明 …

在C#/Net中使用Mqtt

net中MQTT的应用场景 c#常用来开发上位机程序&#xff0c;或者其他一些跟设备打交道比较多的系统&#xff0c;所以会经常作为拥有数据的终端&#xff0c;可以用来采集上传数据&#xff0c;而MQTT也是物联网常用的协议&#xff0c;所以下面介绍在C#开发中使用MQTT。 安装MQTTn…

科普文:一文搞懂jvm实战(二)Cleaner回收jvm资源

概叙 在JDK9中新增了Cleaner类&#xff0c;该类的作用是用于替代finalize方法&#xff0c;更有效地释放资源并避免内存泄漏。 在JEP260提案中&#xff0c;封装了大部分Sun包内部的API之余&#xff0c;还引入了一些新的API&#xff0c;其中就包含着Cleaner这个工具类。Cleaner承…

JavaScript——对象的创建

目录 任务描述 相关知识 对象的定义 对象字面量 通过关键字new创建对象 通过工厂方法创建对象 使用构造函数创建对象 使用原型(prototype)创建对象 编程要求 任务描述 本关任务&#xff1a;创建你的第一个 JavaScript 对象。 相关知识 JavaScript 是一种基于对象&a…

面向物联网行业的异常监控追踪技术解决方案:技术革新与运维保障

在现代高度数字化和互联的环境中&#xff0c;物联网技术已经深入到我们生活的方方面面。特别是在家庭和工业环境中&#xff0c;物联网系列通讯作为连接各类设备的关键枢纽&#xff0c;其稳定性和可靠性显得尤为重要。本文将介绍一种创新的监控系统&#xff0c;旨在实时跟踪和分…

MySQL数据库的主从复制与读写分离

一、MySQL数据库的主从复制 1.主从复制的概述及原理 &#xff08;1&#xff09;主从复制的意义 在实际的生产环境中&#xff0c;如果对数据库的读和写都在同一个数据库服务器中操作,无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此&#xff…

C语言使用先序遍历创建二叉树

#include<stdio.h> #include<stdlib.h>typedef struct node {int data;struct node * left;struct node * right; } Node;Node * createNode(int val); Node * createTree(); void freeTree(Node * node);void preOrder(Node * node);// 先序创建二叉树 int main()…

【高性能服务器】多进程并发模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 对于常见的C/S模型…

事务的影子拷贝-系统架构师(二十)

1、&#xff08;重点&#xff09;企业信息集成按照组织范围分为企业内部的信息集成和外部信息集成。在企业内部信息集成中&#xff0c;&#xff08;&#xff09;实现了不同系统之间的互操作&#xff0c;使的不同系统之间能够实现数据和方法的共享。&#xff08;&#xff09;实现…

QT学习积累——如何提高Qt遍历list的效率

目录 引出Qt遍历list提高效率显示函数的调用使用&与不使用&除法的一个坑 总结自定义信号和槽1.自定义信号2.自定义槽3.建立连接4.进行触发 自定义信号重载带参数的按钮触发信号触发信号拓展 lambda表达式返回值mutable修饰案例 引出 QT学习积累——如何提高Qt遍历list…

CSF视频文件格式转换WMV格式(2024年可用)

如果大家看过一些高校教学讲解视频的话&#xff0c;很可能见过这样一个难得的格式&#xff0c;".csf "&#xff0c;非常漂亮 。 用暴风影音都可以打开观看&#xff0c;会自动下载解码。 但是一旦我们想要利用或者上传视频的时候就麻烦了&#xff0c;一般网站不认这…

为什么PS5运行游戏的效果往往比号称更强大的Xbox Series X更好?

在第九代游戏机即将进入第四个年头之际&#xff0c;有一个问题仍未得到解答&#xff1a;索尼的 PS5 游戏机的性能如何经常超越纸面性能更强大的微软 Xbox X 系列&#xff1f; 几个明显的例子包括《生化危机 4》、《使命召唤&#xff1a;黑色行动&#xff1a;冷战》和新一代更新…

【支撑文档】系统安全保证措施(word原件)

软件安全保证措施word 软件所有全套资料获取进主页或者本文末个人名片直接。

跨平台营销的智能协同:Kompas.ai如何整合多渠道策略

引言 在数字化营销的今天&#xff0c;消费者的注意力分散在多个平台上。品牌要想有效地吸引和保持消费者的关注&#xff0c;就必须采取跨平台营销策略。Kompas.ai&#xff0c;作为一款智能营销工具&#xff0c;能够帮助品牌实现这一目标。 跨平台营销的重要性 跨平台营销能够…

智慧园区大数据云平台建设方案(Word原件)

第一章 项目建设背景及现状 第二章 园区创新发展趋势 第三章 工业园区大数据存在的问题 第四章 智慧工业园区大数据建设目的 第五章 智慧园区总体构架 第六章 系统核心组件 第七章 智慧工业园区大数据平台规划设计 获取方式&#xff1a;本文末个人名片直接获取。 软件资料清单…

超融合服务器挂载硬盘--linux系统

项目中需要增加服务器的硬盘容量&#xff0c;通过超融合挂载了硬盘后&#xff0c;还需要添加到指定的路径下&#xff0c;这里记录一下操作步骤。 一&#xff1a;通过管理界面挂载硬盘 这一步都是界面操作&#xff0c;登录超融合控制云台后&#xff0c;找到对应的服务器&#…

uniapp中实现跳转到外部链接(也就是a标签的功能)

uniapp中实现跳转到外部链接&#xff08;也就是a标签的功能&#xff09; 项目中需要做到跳转到外部链接&#xff0c;网上找了很多都不是很符合自己的要求&#xff0c;需要编译成app后是跳转到游览器打开链接&#xff0c;编译成web是在新窗口打开链接。实现的代码如下&#xff1…

矩阵、混剪、大盘,3大功能升级优化!助力企业高效管理!

在数字化转型的浪潮中&#xff0c;企业对于工具与技术的需求愈发强烈。 为满足市场需求&#xff0c;本月【云略】为各企业上线了便捷功能&#xff0c;赋能企业经营决策和业务增长。 矩阵管理 √【矩阵号管理】抖音支持设置城市IP 内容管理 √【混剪任务】支持关联智能发布计…