JAVA之Java线程核心详解

Java线程核心

1.进程和线程

进程:进程的本质是一个正在执行的程序,程序运行时系统会创建一个进程,并且给每个进程分配独立的内存地址空间保证每个进程地址不会相互干扰。同时,在 CPU 对进程做时间片的切换时,保证进程切换过程中仍然要从进程切换之前运行的位置出开始执行。所以进程通常还会包括程序计数器、堆栈指针。

线程:有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派 CPU 的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

2.线程的创建方式

创建的方式有三种:

  1. 继承Thread
  2. 实现Runnable接口
  3. Callable/Future

import java.util.concurrent.CountDownLatch;public class ThreadDemo1 extends Thread {CountDownLatch countDownLatch;public ThreadDemo1(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ":my thread ");} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}public static void main(String[] args) {// 第一种:使用extends Thread方式CountDownLatch countDownLatch1 = new CountDownLatch(2);for (int i = 0; i < 2; i++) {ThreadDemo1 myThread1 = new ThreadDemo1(countDownLatch1);myThread1.start();}try {countDownLatch1.await();System.out.println("thread complete...");} catch (InterruptedException e) {e.printStackTrace();}}}import java.util.concurrent.CountDownLatch;public class ThreadDemo2  implements Runnable{CountDownLatch countDownLatch;public ThreadDemo2(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + ":my runnable ");} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}public static void main(String[] args) {// 第二种:使用implements Runnable方式CountDownLatch countDownLatch2 = new CountDownLatch(2);ThreadDemo2 myRunnable = new ThreadDemo2(countDownLatch2);for (int i = 0; i < 2; i++) {new Thread(myRunnable).start();}try {countDownLatch2.await();System.out.println("runnable complete...");} catch (InterruptedException e) {e.printStackTrace();}}
}public class ThreadDemo3 implements Callable<Integer> {public static void main(String[] args) {ThreadDemo3 threadDemo03 = new ThreadDemo3();//1、用futureTask接收结果FutureTask<Integer> futureTask = new FutureTask<>(threadDemo03);new Thread(futureTask).start();//2、接收线程运算后的结果try {//futureTask.get();这个是堵塞性的等待Integer sum = futureTask.get();System.out.println("sum="+sum);System.out.println("-------------------");} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <101 ; i++) {sum+=i;}return sum;}
}

3.线程的生命周期

  • 新建状态(New):线程对象被创建后,但还没有调用start()方法时的状态。
  • 就绪状态(Runnable):线程对象调用start()方法后进入就绪状态,表示线程可以被调度执行。
  • 运行状态(Running):线程被调度执行后进入运行状态。
  • 等待状态(WAITING): 线程需要等待其他线程做出一些特定动作(通知或中断)
  • 阻塞状态(Blocked):线程在执行过程中可能因为某些原因被阻塞,例如等待输入输出、线程休眠等。
  • 结束状态(Terminated):线程执行完任务后进入结束状态。

在这里插入图片描述

4.线程的启动和终止

4.1 启动线程

  启动线程是start方法,非run方法

4.2 线程的终止

使用退出标志退出线程

一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环

例如:最直接的方法就是设一个boolean 类型的标志,并通过设置这个标志为 true或 false 来控制 while循环是否退出。定义了一个退出标志 exit,当 exit 为 true 时,while 循环退出,exit 的默认值为 false.在定义 exit时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只能由一个线程来修改 exit 的值。

class FlagThread extends Thread {// 自定义中断标识符public volatile boolean isInterrupt = false;@Overridepublic void run() {// 如果为 true -> 中断执行while (!isInterrupt) {System.out.println("业务处理1....");// 业务逻辑处理Thread.sleep(10000);System.out.println("业务处理2....");}}
}

Interrupt方法结束线

线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。

public static void main(String[] args) throws InterruptedException {
// 创建可中断的线程实例
Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("thread 执行步骤1:线程即将进入休眠状态");try {// 休眠 1sThread.sleep(1000);} catch (InterruptedException e) {System.out.println("thread 线程接收到中断指令,执行中断操作");// 中断当前线程的任务执行break;}System.out.println("thread 执行步骤2:线程执行了任务");}
});
thread.start(); // 启动线程// 休眠 100ms,等待 thread 线程运行起来
Thread.sleep(100);
System.out.println("主线程:试图终止线程 thread");
// 修改中断标识符,中断线程
thread.interrupt(); // 首先会改变当前线程的阻塞状态 true 。同时如何线程调用 sleep wait 这些阻塞的方法。那么会抛出 InterruptedException 异常
}

线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。

stop 方法终止线程

程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。

5.线程间通信

  1. wait()和notify()方法:wait()方法使线程进入等待状态,直到其他线程调用notify()或notifyAll()方法将其唤醒。notify()方法唤醒一个等待中的线程,notifyAll()方法唤醒所有等待中的线程。
  • wait(long timeout)和notify()方法:wait(long timeout)方法使线程进入等待状态,直到其他线程调用notify()方法将其唤醒,或者等待时间超过指定的timeout时间。notify()方法唤醒一个等待中的线程。
  • join()方法:join()方法使一个线程等待另一个线程执行完毕。当一个线程调用另一个线程的join()方法时,当前线程将被阻塞,直到另一个线程执行完毕。
  • Lock和Condition接口:Lock接口提供了比synchronized关键字更灵活的锁机制,Condition接口提供了更灵活的等待/通知机制。通过Lock接口的lock()方法获取锁,unlock()方法释放锁;通过Condition接口的await()方法使线程等待,signal()方法唤醒一个等待中的线程,signalAll()方法唤醒所有等待中的线程。
  • BlockingQueue阻塞队列:BlockingQueue是一个支持阻塞操作的队列,当队列为空时,获取元素的线程将被阻塞,直到队列中有可用元素;当队列满时,插入元素的线程将被阻塞,直到队列有空闲位置。

6.线程池

6.1 线程池原理

提交一个任务到线程池中,线程池的处理流程如下:

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程
  3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

线程池的好处:

  • 降低资源消耗:通过重复利用已创建的线程降低线程的创建和销毁造成的不必要资源消耗
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就立即开始执行
  • 提高线程的可管理性:统一分配、监控、调优

6.2 常见的线程池实现

线程池说明
newCachedThreadPool创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行
很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造
的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并
从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资
源。
newFixedThreadPool创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大
多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,
则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何
线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之
前,池中的线程将一直存在。
newScheduledThreadPool创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
newSingleThreadExecutorExecutors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程
池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

6.3 ThreadPoolExecutor

  系统提供的线程池的实现都有各自的优缺点。我们在实际的使用中更多的是自定义线程池的实现

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) 

各个参数的含义:

corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列
handler:超出线程范围和队列容量的任务的处理程序

使用的是阻塞队列:

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • SynchronousQueue
  • PriorityBlockingQueue

拒绝策略分类:

  • AbortPolicy:直接抛出异常,默认策略
  • CallerRunsPolicy:用调用者所在的线程来执行任务
  • DiscardOldestPolicy:丢弃阻塞对类中靠最前的任务,并执行当前任务
  • DiscardPolicy:直接丢弃任务

提交一个任务到线程池中,线程池的处理流程如下:

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3. 判断线程池里的线程是否达到最大线程数,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

在这里插入图片描述

6.4 线程池调优

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

  • 任务的性质:CPU密集型任务、IO密集型任务和混合型任务

  • 任务的优先级:高中低

  • 任务的执行时间:长中短

  • 任务 的依赖性;是否依赖其他系统资源

  • CPU密集型任务应配置尽可能小的线程,如配置NCPU+1个线程的线程池,

  • IO密集型任务线程并不是一直在执行任务 ,则应配置尽可能多的线程,如2*NCPU 。

可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理,它可以让优先级高的任务先执行。

7.Synchronized

  同步、重量级锁,synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入临界区,同时还可以保证共享变量的内存可见性(单台JVM内)

synchronized锁对象:

  • 普通同步方法,锁的是当前实例对象
  • 静态同步方法,锁的是当前类的class对象
  • 同步代码块,锁的是括号里的对象(实例对象或者class对象)

synchronized的锁优化

  • 无锁
  • 偏向锁:为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,主要尽可能避免不必须要的CAS操作,如果竞争锁失败,则升级为轻量级锁
  • 轻量级锁:自旋方式,该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁(循环方式)
  • 重量级锁:阻塞方式
  • 锁消除: 有些代码明明不用加锁,结果还给加上锁,这时编译器会判断锁没有什么必要,就直接把锁去掉了
  • 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。例如for循环内部获取锁

8.AQS

AbstractQueuedSynchronizer,同步器,实现JUC核心基础组件,解决了子类实现同步器时涉及到的大量细节性问题,例如:同步状态、FIFO同步队列等,采用模板方法模式,AQS实现了大量的通用方法,子类通过继承方式实现其抽象方法来管理同步状态

同步锁获取与释放:

  • 独占式
  • 共享式
package com.boge.flow.aqs;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class AQSDemo {// 共享的资源private static  int count = 0;private static Lock lock = new ReentrantLock();/*** 造成数据安全问题的原因*   1.原子性:执行的最小单元  要么都执行。要么都不执行*   2.可见性:*   3.有序性:*/// 定义一个方法去操作资源public static  void incr(){try {Thread.sleep(10);lock.lock(); // 加锁// ...lock.lock();count ++; // 原子  可见性}catch (Exception e){}finally {lock.unlock();lock.unlock(); // 释放锁}}public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(100);// 多个线程去操作共享资源for (int i = 0; i < 100; i++) {new Thread(new Runnable() {@Overridepublic void run() {incr();latch.countDown();}}).start();}latch.await(); // 阻塞// 获取 共享的数据System.out.println("count = " + count);}
}

在这里插入图片描述

在这里插入图片描述

9.ThreadLocal

  一种解决多线程环境下成员变量的问题的方案,但是与线程同步无关,其思路就是为每个线程创建一个单独的变量副本。从而每个线程都可以独立的改变自己所拥有的变量副本,而不会影响其他线程对应的变量副本 , ThreadLocal不是用于解决共享变量的问题,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制

  ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离,相比于synchronized的做法是用空间来换时间。

  ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存key value键值对的能力。

  弱引用的目的是为了防止内存泄露,如果是强引用那么ThreadLocal对象除非线程结束否则始终无法被回收,弱引用则会在下一次GC的时候被回收。

  但是这样还是会存在内存泄露的问题,假如key和ThreadLocal对象被回收之后,entry中就存在key为null,但是value有值的entry对象,但是永远没办法被访问到,同样除非线程结束运行。

  但是只要ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出现这个问题的。

在这里插入图片描述

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

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

相关文章

微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用

&#x1f3f7;️个人主页&#xff1a;鼠鼠我捏&#xff0c;要死了捏的主页 &#x1f3f7;️系列专栏&#xff1a;Golang全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

猫头虎博客分享:深入解析 Visual Studio Code 1.86 版本新特性

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

测试文章笔记-SQL3种优化方法

SQL语句优化&#xff1a; 本质&#xff1a;降低执行时间 **核心思路&#xff1a;**找到执行计划中开销较高的操作&#xff0c;改写SQL语句或改变表访问方式调整执行计划。 举例&#xff1a; 1.使用索引替代全表扫描&#xff08;索引&#xff1a;是帮助MysQL高效获取数据的数…

【JVM】打破双亲委派机制

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;JVM ⛺️稳中求进&#xff0c;晒太阳 打破双亲委派机制 打破双亲委派机制三种方法 自定义类加载器 ClassLoader包含了四个核心方法 //由类加载器子类实现&#xff0c;获取二进制数据调用…

MySQL之json数据操作

1 MySQL之JSON数据 总所周知&#xff0c;mysql5.7以上提供了一种新的字段格式json&#xff0c;大概是mysql想把非关系型和关系型数据库一口通吃&#xff0c;所以推出了这种非常好用的格式&#xff0c;这样&#xff0c;我们的很多基于mongoDB的业务都可以用mysql去实现了。当然…

NumPy模块完结篇:深入探讨和高效利用【第85篇—NumPy模块】

NumPy模块完结篇&#xff1a;深入探讨和高效利用 NumPy是Python中用于科学计算的核心库之一&#xff0c;提供了高性能的多维数组对象&#xff08;numpy.ndarray&#xff09;以及许多用于操作这些数组的函数。在前面的几篇博客中&#xff0c;我们介绍了NumPy的基础知识、数组操…

电子商务跨境电商大数据的关键技术之—主流电商大数据采集

大数据采集是指通过各种技术手段和工具收集、获取和提取大规模数据的过程。在信息时代&#xff0c;各种互联网、物联网、移动设备等的普及和应用&#xff0c;产生了海量的数据&#xff0c;这些数据被称为大数据。大数据采集就是对这些数据进行收集和抓取&#xff0c;以获得有意…

手把手一起开发SV4E-I3C设备(二)

JEDEC DDR5 SPD Hub Devices例程 DDR5生态系统的核心是SidebandBus Protocol 参考下图&#xff0c;可以将SV4E-I3C的端口1声明为主服务器(模拟主机控制器)&#xff0c;并且它可以属于SV4E-I3C上的一个总线。端口2可以作为SPD Hub DUT的Local Bus侧的从站连接。这个从站可以被…

12(S)-HETE ELISA kit--灵敏的ELISA试剂盒

灵敏的ELISA试剂盒&#xff0c;能够检测任何物种的培养上清液和血浆中的12(S)-HETE HETE是由脂氧合酶代谢花生四烯酸产生的副产物。12(S)-HETE是12(S)-氢过氧四烯酸&#xff08;12(S)-HpETE&#xff09;还原的立体特异性羟基产物&#xff0c;其本身是花生四烯酸的12-脂氧酶代谢…

23年秋招结束,同学们陆陆续续拿到心仪的offer!24年秋招出发!

续接上次上岸同学的分享&#xff1a;还在担心秋招吗&#xff1f;看看24届已上岸同学的经验分享&#xff01; 秋招的时间过程相同&#xff0c;但经历却各不相同。学历、专业似乎都影响着同学们的面试经历和感受。校招的面试毫无疑问学历的加持是巨大的优势&#xff0c;学历好会…

票房25亿!《热辣滚烫》的创造性模仿,普通人赚钱的落地方法

最近很火的电影《热辣滚烫》包含了我们很多普通人做点小事儿&#xff0c;赚点小钱非常落地的方法&#xff0c;叫做创造性模仿。 很多人说《热辣滚烫》是翻拍的日本《百元之恋》&#xff0c;知道这个有什么用&#xff1f;就证明贾玲不是那么优秀吗&#xff1f;对我们普通人想赚…

全新超大屏三防加固平板为什么做到Intel core i7的高性能

在小编看来&#xff0c;一款加固三防平板电脑结构规划的思路&#xff1a;一定是要在三防规划中留意取舍的。如果是三防要求高的的商品&#xff0c;则需要将三防规划作为一个主线来做&#xff0c;其他方面环绕三防要求打开&#xff0c;并要有清晰的三防思路和总体思想&#xff0…

java面试微服务篇

目录 目录 SpringCloud Spring Cloud 的5大组件 服务注册 Eureka Nacos Eureka和Nacos的对比 负载均衡 负载均衡流程 Ribbon负载均衡策略 自定义负载均衡策略 熔断、降级 服务雪崩 服务降级 服务熔断 服务监控 为什么需要监控 服务监控的组件 skywalking 业务…

【c++】const引用

Hello everybody!今天给大家讲讲有关const引用部分的知识&#xff0c;因为这部分知识涉及到const与引用直接如何灵活的运用&#xff0c;且不太好理解。所以我认为讲一下这里的知识还是很有必要的&#xff01; 1.权限可缩小 首先&#xff0c;当我们定义了a&#xff0c;在给a取别…

人工智能学习与实训笔记(二):神经网络之图像分类问题

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 目录 二、图像分类问题 2.1 尝试使用全连接神经网络 2.2 引入卷积神经网络 2.3 分类函数Softmax 2.4 交叉熵损失函数 2.5 学习率优化算法 2.6 图像预处理算法 2.6.1 随机改变亮暗、对比度和颜色等 …

这才是大学生该做的副业,别再痴迷于游戏了!

感谢大家一直以来的支持和关注&#xff0c;尤其是在我的上一个公众号被关闭后&#xff0c;仍然选择跟随我的老粉丝们&#xff0c;你们的支持是我继续前行的动力。为了回馈大家长期以来的陪伴&#xff0c;我决定分享一些实用的干货&#xff0c;这些都是我亲身实践并且取得成功的…

Typora的下载安装(文末有安装包,2024亲测可用)

一、安装步骤 1、首先下载安装包&#xff0c;解压到你的目录下面 2、进入到解压后的文件夹下面&#xff0c;看到如下的内容&#xff1a; 3、双击exe文件开始安装&#xff0c;选择安装目录&#xff0c;并记下安装地址&#xff01;&#xff01;&#xff01; 选择创建桌面快捷方…

2024年腾讯云4核8G12M轻量应用服务器测评,2月更新

4核8G服务器支持多少人同时在线访问&#xff1f;阿腾云的4核8G服务器可以支持20个访客同时访问&#xff0c;关于4核8G服务器承载量并发数qps计算测评&#xff0c;云服务器上运行程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&…

树和二叉树的基本知识

一、树的概念及结构 1.树的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 有一个 特殊…

2024春节过后,抖店爆款出单类目产品,应季产品需要提前布局

我是王路飞。 之前给你们强调过&#xff0c;“应季品”是最容易爆单的产品类型&#xff0c;没有之一。 那么在2024年春节刚过的现在&#xff0c;当别人还沉浸在过年的氛围中时&#xff0c;有心的商家早就开始布局未来三个月的爆款类目和产品了。 今天的内容&#xff0c;就给…