JAVA面试部分——后端-线程前篇

3.1 线程和进程

在计算机科学中,进程和线程是操作系统管理资源的两种不同方式。

  • 进程(Process):是程序在计算机上的一次执行活动。每个进程都有自己的内存空间,包括代码、数据和系统资源。一个进程可以包含多个线程。进程之间相互独立,各自拥有独立的内存空间和系统资源,彼此不会直接共享数据,通信需要通过进程间通信机制来实现。

  • 线程(Thread):是进程中的一个执行单元。一个进程中的多个线程共享相同的内存空间和系统资源,可以共享数据。线程在同一进程中运行,相对于进程而言更加轻量级,创建和销毁的开销更小,线程间的切换开销也更小。

Java 中的线程是通过 java.lang.Thread 类来实现的。Java 程序运行时会启动一个主线程(Main Thread),可以通过创建 Thread 对象来创建新的线程,并调用 start() 方法来启动线程的执行。

多线程可以充分利用多核处理器的计算能力,提高程序的执行效率。但同时,多线程编程也需要处理同步、死锁、竞态条件等并发问题,需要谨慎编写以确保线程安全。

3.2 线程的实现方式?

继承Thread类,实现Runnable接口,实现Callable接口,创建线程池。

3.3 有三个线程,制定一种机制,保证线程a在线程b之前运行,线程b在线程c之前运行
你可以使用线程同步工具来实现这种机制。其中最常见的是使用Java的wait()和notify()方法。首先,为每个线程创建一个类,并在类中定义一个共享的Object,我们称之为syncObj。这个对象用于线程间的通信。
java
public class ThreadA extends Thread {  private Object syncObj;  public ThreadA(Object syncObj) {  this.syncObj = syncObj;  }  @Override  public void run() {  synchronized (syncObj) {  // 线程a的代码  System.out.println("Thread A is running");  // 通过notify唤醒线程b  syncObj.notify();  }  }  
}  public class ThreadB extends Thread {  private Object syncObj;  public ThreadB(Object syncObj) {  this.syncObj = syncObj;  }  @Override  public void run() {  synchronized (syncObj) {  // 线程b的代码  System.out.println("Thread B is running");  // 通过notify唤醒线程c  syncObj.notify();  }  }  
}  public class ThreadC extends Thread {  private Object syncObj;  public ThreadC(Object syncObj) {  this.syncObj = syncObj;  }  @Override  public void run() {  synchronized (syncObj) {  // 线程c的代码  System.out.println("Thread C is running");  }  }  
}
//然后在主程序中创建并启动这些线程:
​
java
public static void main(String[] args) {  Object syncObj = new Object();  ThreadA threadA = new ThreadA(syncObj);  ThreadB threadB = new ThreadB(syncObj);  ThreadC threadC = new ThreadC(syncObj);  threadA.start();  threadB.start();  threadC.start();  
}
这样,线程a会在其他线程之前运行,然后唤醒线程b,线程b再唤醒线程c。但是需要注意的是,Java的wait()和notify()方法在实际使用时需要非常小心,因为如果没有正确地使用它们,可能会导致死锁或者无法预料的后果。例如,如果线程b没有在调用notify()之前唤醒线程a,那么线程a可能会永远被阻塞。
3.4 Runnable与Callable的区别?

实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果; Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。

3.5 list中有20条数据分为5个线程每个线程4个数据,每条数据+1,怎么拿到他们组合成一个list?

采用Callable的异步算法(call方法有返回值,run方法没有返回值)。

在Java中,你可以使用ExecutorServiceCallable来实现你的需求。Callable是一种可以返回结果的任务接口,它的对象可以被ExecutorService线程池执行。

import java.util.*;  
import java.util.concurrent.*;  public class Main {  public static void main(String[] args) throws InterruptedException, ExecutionException {  // 假设的20条数据  List<Integer> data = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20));  // 分成5组,每组4个数据  List<List<Integer>> groups = new ArrayList<>();  for (int i = 0; i < data.size(); i += 4) {  groups.add(new ArrayList<>(data.subList(i, Math.min(data.size(), i + 4))));  }  // 创建一个固定大小的线程池  ExecutorService executor = Executors.newFixedThreadPool(5);  // 对每个组执行加1操作,并收集结果  List<Future<List<Integer>>> futures = new ArrayList<>();  for (List<Integer> group : groups) {  futures.add(executor.submit(new Callable<List<Integer>>() {  @Override  public List<Integer> call() throws Exception {  return processGroup(group);  }  }));  }  // 等待所有操作完成,并收集结果  List<Integer> combinedList = new ArrayList<>();  for (Future<List<Integer>> future : futures) {  combinedList.addAll(future.get());  }  System.out.println(combinedList); // 打印结果  }  private static List<Integer> processGroup(List<Integer> group) {  // 对每个元素加1操作  List<Integer> result = new ArrayList<>();  for (Integer num : group) {  result.add(num + 1);  }  return result;  }  
}
3.6 线程池是什么?什么情况下需要使用线程池?使用线程池的好处

以下是一些需要使用线程池的情况:

  • 并发任务处理:线程池可以用于处理并发的任务,例如处理请求、批量处理数据、并行计算等。通过线程池,可以管理和复用线程,提高任务的执行效率。

  • 异步任务执行:线程池可以用于执行异步任务,将任务提交给线程池后,可以立即返回并继续执行后续代码,不必等待任务完成。适用于需要在后台执行耗时任务,同时不阻塞主线程的场景。

  • 定时任务调度:线程池提供了定时任务执行的功能,可以周期性地执行任务或在指定的时间点执行任务。适用于需要按计划执行任务的场景,例如定时任务、定时检查等。

  • 资源池管理:线程池可以用于管理资源池,例如数据库连接池、线程池等。通过线程池,可以复用和管理资源,提高资源利用率,同时控制资源的并发访问。

  • 并行计算:线程池可以用于并行计算,将计算任务分解为多个子任务,分配给线程池中的线程并行执行,加速计算过程。适用于需要高性能并行计算的场景,如数据处理、图像处理等。

  • 长时间运行的任务:线程池适用于长时间运行的任务,可以控制线程的生命周期,避免频繁创建和销毁线程的开销,提高系统的稳定性和性能。

总之,无论是在Web服务器、桌面应用程序还是大规模分布式系统中,线程池都可以提供更好的性能、可扩展性和资源管理。选择合适的线程池并进行适当的调优,可以有效地提高系统的性能和稳定性。

3.7 线程池:核心线程数,最大线程数,队列的选择,线程初始化的时候线程数量是怎么变化的?
  • 核心线程数:线程池中保持活动线程的最小数量。这些线程会在进程启动时创建,并且只要线程池需要,就会持续存在。核心线程数通常由corePoolSize属性设置。

  • 最大线程数:线程池中允许存在的最大线程数量。如果队列满了并且已经达到了核心线程数,那么线程池会根据需要创建更多的线程,直到达到这个最大值。最大线程数通常由maximumPoolSize属性设置。

  • 队列的选择:当有新的任务提交给线程池,而当前线程数未达到核心线程数时,这些任务会被放在一个队列中等待。常见的队列类型有:无界队列(如LinkedBlockingQueue)、有界队列(如ArrayBlockingQueue)以及优先队列(如PriorityBlockingQueue)等。队列的选择需要根据实际需求来决定。

    线程池的队列选择取决于多个因素,包括任务的特性、线程池的实现方式以及具体的应用场景。以下是关于几种常见队列的讨论:

    1. SynchronousQueue:一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者。如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。

    2. ArrayBlockingQueue:是一种有界队列,它实现了BlockingQueue接口。此队列按 FIFO(先进先出)原则对元素进行排序。此队列的容量必须指定,在创建后就不能改变。如果尝试向已满的队列中添加元素,操作就会阻塞。如果尝试从空的队列中获取元素但该队列已经不再有可用元素(即队列为空),则操作也会阻塞。

    3. LinkedBlockingQueue:是一种无界队列(LinkedBlockingQueue的容量仅受制于内存的大小),它实现了BlockingQueue接口。此队列按照 FIFO(先进先出)原则对元素进行排序。

    4. PriorityBlockingQueue:是一种无界队列,它实现了BlockingQueue接口。此队列按照元素的优先级对元素进行排序。如果多个元素具有相同的优先级,则将按照它们的自然顺序(例如它们的自然排序或者插入顺序)进行排序。

    5. 选择队列时需要考虑任务的数量、任务的优先级以及系统的内存情况等因素。例如,如果系统需要处理大量低优先级的任务,那么选择LinkedBlockingQueue可能更为合适;如果任务数量有限,那么可以选择ArrayBlockingQueue,这样可以避免队列过大导致的内存压力。另外,如果任务具有不同的优先级,可以选择PriorityBlockingQueue来保证高优先级的任务能够优先得到处理。

  • 线程初始化的时候线程数量:初始时,线程池中的线程数量通常等于核心线程数。如果任务提交的速度超过了核心线程的处理速度,那么队列会逐渐增长,直到达到最大队列长度。如果当前线程数已经达到了最大线程数,但队列已经满了,那么新提交的任务将会被拒绝。

在实际应用中,选择合适的线程池和参数配置需要根据具体的应用场景和需求来决定。例如,对于处理大量并发请求的Web应用,可以选择ThreadPoolExecutorForkJoinPool;对于需要处理大量计算任务的场景,可以选择ForkJoinPoolExecutorService等。

3.8 说一下常见的几个线程池?(Java里面有4个线程池)

常见的线程池有以下几种:

  • newSingleThreadExecutor():创建一个只有一个线程的线程池。这个线程池会保证所有的任务按照提交的顺序一个一个地执行。如果这个线程池中的线程因为任务异常结束,那么它会立即创建一个新的线程替代。

  • newFixedThreadPool():创建一个定长的线程池。这个线程池会一次性创建指定数量的线程,然后每次提交的任务都会被一个线程立即执行。如果所有线程都在执行任务,那么新的任务就会进入等待队列,等待有线程空闲时再执行。

  • newCacheThreadPool():创建一个可缓存的线程池。这个线程池的大小会根据需要动态调整。如果线程池的大小超过了处理任务所需的线程数,那么就回收部分线程(一般是60S内未执行)。

  • newScheduledThreadPool():创建一个定长的线程池,支持定时和周期性任务执行。

  • newSingleThreadScheduledExecutor():创建一个单例的线程池,支持定期或延时执行任务。

以上就是Java中常见的五种线程池,了解它们可以帮助我们更好地利用多线程处理任务,提高程序的执行效率。

3.9 线程怎么释放的?

线程池中的线程可以通过以下方式释放:

  • 执行器自动关闭旧线程:当线程池中的线程完成任务后,执行器会自动关闭状态为非运行状态的线程。

  • 线程自愿退出:在线程没有获取到任务,并且在指定的等待时间内也没有任务可执行时,线程会自愿退出,通过processWorkerExit()方法可以证实这一点。

  • 定时器触发线程回收:对于核心线程,如果长时间没有任务执行,定时器会触发回收线程。

需要注意的是,线程池中的线程数量没有固定,可以根据需要动态调整。当任务量增大时,线程池会自动增加线程数量;当任务量减少时,线程池会自动减少线程数量。同时,线程池中的线程状态也不同,有些是核心线程,有些是非核心线程。核心线程在空闲1分钟后也会被回收。

在Spring框架中,可以通过配置线程池来管理线程的释放。以下是一些常用的配置选项:
​
1.使用ThreadPoolTaskExecutor类创建线程池:
java
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
executor.setCorePoolSize(5);  
executor.setMaxPoolSize(10);  
executor.setKeepAliveSeconds(60);  
executor.setQueueCapacity(25);  
executor.setThreadNamePrefix("MyThreadPool-");  
executor.initialize();
​
2.在Spring的配置文件中配置线程池:
xml
<task:executor id="myExecutor" pool-size="5-10" keep-alive-seconds="60" queue-capacity="25" thread-name-prefix="MyThreadPool-" />
​
3.在Spring的配置文件中配置ThreadPoolTaskExecutor:
xml
<task:executor id="myExecutor" pool-size="5-10" keep-alive-seconds="60" queue-capacity="25" thread-name-prefix="MyThreadPool-" />
​
这些配置选项可以设置线程池的核心线程数、最大线程数、线程的存活时间、队列容量以及线程名称前缀等参数。根据实际需求,可以调整这些参数来优化线程池的性能。
3.10 为什么要线程同步?并说一说线程同步的方式

线程同步的目的是为了保证多个线程之间的协同工作,避免出现数据竞争和不一致的情况。线程同步可以确保在同一时刻只有一个线程可以访问共享资源,避免多个线程同时对同一份数据进行修改或操作,保证数据的一致性和完整性。

线程同步的方式有多种,常见的有以下几种:

  • 互斥锁(Mutex):通过使用互斥锁,只有一个线程可以在同一时刻持有这个锁,其他线程必须等待该线程释放锁之后才能访问共享资源。

  • 信号量(Semaphore):信号量是一种更高级的同步机制,可以用于控制多个线程对共享资源的访问。信号量可以初始化为一个特定的值,表示可以同时访问共享资源的线程数量。当线程访问共享资源时,需要获取一个信号量许可,当访问完成后释放许可,让其他线程可以继续访问。

  • 条件变量(Condition):条件变量允许线程等待某个条件满足后才继续执行。它可以让线程等待特定的信号或事件发生,而不会浪费CPU时间。

  • 读写锁(Read-Write Lock):读写锁是一种特殊的锁机制,适用于读操作比写操作更频繁的情况。它允许多个线程同时进行读操作,但只允许一个线程进行写操作。

  • 临界区(Critical Section):临界区是一段必须互斥执行的代码,用于保护共享资源的访问。只有拥有临界区的线程才能执行其中的代码,其他线程必须等待。

这些线程同步方式各有优缺点,应根据具体情况选择适合的同步方式。

3.11 线程池怎么从等待队列中拿任务的?(头结点)

线程池从等待队列中拿任务的核心过程如下:

  • 线程池首先会检查等待队列是否为空,如果等待队列为空,则无法从队列中获取任务。

  • 如果等待队列不为空,线程池会尝试从队列头部获取一个任务。

  • 如果无法从队列头部获取任务,说明队列已经被占用,这时线程池会尝试创建新的线程来执行任务。

  • 如果无法创建新的线程,线程池会采取拒绝策略来处理无法执行的任务。

通过以上步骤,线程池就可以从等待队列中获取任务并执行。

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

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

相关文章

内容分发功能升级!一站式搞定文案生成/违规检测/一键分发全流程

随着社交媒体的不断发展&#xff0c;越来越多的企业开始布局新媒体矩阵&#xff0c;从集团总部到区域门店、个人销售&#xff0c;从全品类到细分垂直类目、从单一平台到多平台&#xff0c;试图让品牌影响力覆盖更广泛群体&#xff0c; 当然&#xff0c;随之而来的&#xff0c;如…

深度学习”和“多层神经网络”的区别

在讨论深度学习与多层神经网络之间的差异时&#xff0c;我们必须首先理解它们各自是什么以及它们在计算机科学和人工智能领域的角色。 深度学习是一种机器学习的子集&#xff0c;它使用了人工神经网络的架构。深度学习的核心思想是模拟人脑神经元的工作方式&#xff0c;以建立…

RuntimeError: Placeholder storage has not been allocated on MPS device!解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

java 匿名内部类

匿名内部类&#xff08;类没有名字&#xff09;&#xff1a; 1、什么是内部类&#xff1f; 内部类&#xff1a;在类的内部又定义了一个新的类&#xff0c;被称为内部类。 2、内部类的分类&#xff1a; 静态内部类&#xff1a;类似于静态变量 实例内部类&#xff1a;类似于实例变…

Web安全测试基础

SQL注入 当下最常用的一个攻击手段&#xff0c;就是通过SQL命令插入到Web表单中或页面请求查询字符串中&#xff0c;最终达到欺骗服务器执行恶意的SQL语句的目的&#xff0c;SQL注入一旦成功&#xff0c;轻则直接绕开服务器验证&#xff0c;直接登录成功&#xff0c;重则将服务…

【Flutter 开发实战】Dart 基础篇:条件循环表达式

Dart 是一门强大的编程语言&#xff0c;其条件和循环表达式在编写灵活、高效代码中起着关键作用。本文将深入介绍 Dart 中的条件表达式、循环表达式、以及关键字如 for、while、do...while、break、continue 等内容&#xff0c;旨在为初学者提供清晰的指导。 1. 条件表达式 D…

Netty Channel 详解

优质博文&#xff1a;IT-BLOG-CN 一、Netty 服务端启动过程 【1】创建服务端Channel&#xff1b; 【2】初始化服务端Channel&#xff1b; 【3】注册Selector&#xff1b; 【4】端口绑定&#xff1a;我们分析源码的入口从端口绑定开始&#xff0c;ServerBootstrap的bind(int in…

(每日持续更新)jdk api之File基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

libcurl使用默认编译的winssl进行https的双向认证

双向认证&#xff1a; 1.服务器回验证客户端上报的证书 2.客户端回验证服务器的证书 而证书一般分为&#xff1a;1.受信任的根证书&#xff0c;2不受信任的根证书。 但是由于各种限制不想在libcurl中增加openssl&#xff0c;那么使用默认的winssl也可以完成以上两种证书的双…

可单独设定检测电压/解除电压的电压检测器IC“NV3600”系列和窗口电压检测器IC“NV3601”系列 发布上市

解决各种电压监测难题、用于车载设备和工业设备 可单独设定检测电压/解除电压的电压检测器IC“NV3600”系列 和窗口电压检测器IC“NV3601”系列 发布上市 日清纺微电子发布要上市两款用于车载设备和工业设备的电压检测器IC&#xff0c;一款是可单独设定检测电压和解除电压并可设…

多汗症的病因是什么?

多汗症的病因可以分为多种因素&#xff0c;其中一些是内在的疾病因素&#xff0c;一些是外在的环境因素。 首先&#xff0c;遗传因素是多汗症的一个重要原因。多汗症具有一定的遗传倾向&#xff0c;如果家族中有其他成员也患有多汗症&#xff0c;那么个体患有多汗症的风险可能…

Netty开篇——BIO章(二)

Java BIO 基本介绍 属于传统的 java io编程&#xff0c;内容见java.io包中BIO(blocking I/O) : 同步阻塞&#xff0c;服务器实现模式为个连接一个线程&#xff0c;即客户端有连接请求时服务器端就需要启动一个线程进行处理&#xff0c;如果这个连接不做任何事情会造成不必要的线…

数字化转型的关键:JVS低代码的列表页导入功能解析

在当今数字化时代&#xff0c;数据的管理和迁移变得至关重要。为了满足广大用户的需求&#xff0c;JVS低代码精心打造了“列表页导入功能”。这个功能专为高效处理列表页数据而设计&#xff0c;为用户提供了简单、便捷的数据导入、导出和模板下载服务。 列表页导入功能 列表页…

1、理解Transformer:革新自然语言处理的模型

目录 一、论文题目 二、背景与动机 三、卖点与创新 四、解决的问题 五、具体实现细节 0. Transformer 架构的主要组件 1. 注意力、自注意力&#xff08;Self-Attention&#xff09;到多头注意力&#xff08;Multi-Head Attention&#xff09; 注意力到底是做什么的&…

基于JAVA+ssm智能旅游线路规划系统设计与实现【附源码】

基于JAVAssm智能旅游线路规划系统设计与实现【附源码】 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql…

Neo4j恢复

主要记录windows环境下从备份文件中恢复Neo4j&#xff0c; Linux环境同理 备份在上一篇中有介绍&#xff0c;参考: Neo4j备份-CSDN博客 误删数据 为了模拟误删除场景&#xff0c;我们查询Person&#xff0c;并模拟误操作将其进行删除&#xff1b; match(p:Person) return …

SQL基础知识3

一、删除数据 1、delete操作 删除之前一定要查询一下&#xff0c;确保删除的数据是对的 逻辑删除&#xff1a;在表中新增一个字段&#xff1a;flag/status 二、更新数据 本质上的逻辑删除 三、查询数据 1、联表查询 1、内连接 交集的部分叫内连接 小知识&#xff1a;一般…

Spark: 检查数据倾斜的方法以及解决方法总结

1. 使用Spark UI Spark UI提供了一个可视化的方式来监控和调试Spark作业。你可以通过检查各个Stage的任务执行时间和数据大小来判断是否存在数据倾斜。 任务执行时间: 如果某个Stage中的大部分任务很快完成&#xff0c;但有少数任务执行时间非常长&#xff0c;这可能是数据倾…

thinkphp美容SPA管理系统源码带文字安装教程

thinkphp美容SPA管理系统源码带文字安装教程 运行环境 服务器宝塔面板 PHP 7.0 Mysql 5.5及以上版本 Linux Centos7以上 基于thinkphp3.23B-JUI1.2开发&#xff0c;权限运用了Auth类认证&#xff0c;权限可以细分到每个功能&#xff0c; 增删改查功能一应俱全&#xff0c;整合了…

LeetCode_5_中等_最长回文子串

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 动态规划2.2 中心扩展算法 1. 题目 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#…