面试题004-Java-Java多线程(下)

ops/2024/9/23 0:29:38/

JavaJava_0">面试题004-Java-Java多线程>Java多线程(下)

这里写目录标题

  • 面试题004-Java-Java多线程>Java多线程(下)
    • 题目自测
    • 题目答案
      • 1. synchronized 关键字的作用?
      • 2. volatile 关键字的作用?
      • 3. synchronized 和 volatile 的区别?
      • 4. synchronized 和 ReentrantLock 的区别?
      • 5. ThreadLocal有什么用?
      • 6. 线程池有什么用?为什么不推荐使用内置线程池?
      • 7. 如何自定义线程池?
      • 8. Java线程池有哪些参数?阻塞队列有几种?拒绝策略有几种?
      • 9. 线程池处理任务的流程了解吗?
      • 10. 如何给线程池命名?为什么建议给线程池命名?
    • 参考资料

题目自测

  • 1. synchronized 关键字的作用?
  • 2. volatile 关键字的作用?
  • 3. synchronized 和 volatile 的区别?
  • 4. synchronized 和 ReentrantLock 的区别?
  • 5. ThreadLocal有什么用?
  • 6. 线程池有什么用?为什么不推荐使用内置线程池?
  • 7. 如何自定义线程池?
  • 8. Java线程池有哪些参数?阻塞队列有几种?拒绝策略有几种?
  • 9. 线程池处理任务的流程了解吗?
  • 10. 如何给线程池命名?为什么建议给线程池命名?

题目答案

1. synchronized 关键字的作用?

答:在Java中synchronized关键字用于实现线程同步,主要解决是多个线程之间访问相同资源不会发生数据不一致的问题。它可以作用于方法或代码块上,以保证同一时间只有一个线程可以执行被同步的方法或代码块。

  • 同步实例方法: 表示该方法在同一时间只能由一个线程访问同一个实例。

    public class SynchronizedExample {public synchronized void synchronizedMethod() {// 同步代码}
    }
    
  • 同步静态方法:表示该方法在同一时间只能由一个线程访问同一个类的所有实例。

    public class SynchronizedExample {public static synchronized void synchronizedStaticMethod() {// 同步代码}
    }
    
  • 同步代码块:表示该代码块在同一时间只能由一个线程访问该对象。

    public class SynchronizedExample {private final Object lock = new Object();public void synchronizedBlock() {synchronized (this) {// 同步代码}}
    }
    

2. volatile 关键字的作用?

答:在Java中volatile关键字用于声明变量的可见性和防止指令重排,从而提供一种轻量级的同步机制。
在可见性方面,用volatile修饰的变量,来确保一个线程对该变量的修改对其他线程立即可见。
在防止指令重排方面,volatile会禁止JVM对变量操作的指令进行重新排序。

3. synchronized 和 volatile 的区别?

答:synchronized和volatile两个关键字都用于实现线程同步的机制。

  • volatile是线程同步的轻量级实现,volatile性能比synchronized要好。volatile关键字只能用于变量,而synchronized关键字可以修饰方法和代码块。
  • volatile能保证数据的可见性,但不能保证数据的原子性。synchronized两者都能保证。
  • volatile主要解决变量在多个线程之间的可见性,而synchronize解决的是多个线程之间访问资源的同步性。

4. synchronized 和 ReentrantLock 的区别?

答:synchronized和ReentrantLock都是Java中用于实现线程同步的机制,并且都是可重入锁。

  • synchronized:
    • Java语言的内置关键字,用于对代码块或方法进行同步。
    • 简单易用,直接在方法或代码块上使用即可。
    • 由JVM实现,使用方便,但功能较为有限。
  • ReentrantLock:
    • ReentrantLock是java.util.concurrent.locks包中的类,提供了更灵活和丰富的锁机制(响应中断、尝试获取锁、使用条件变量等)。
    • 需要显示的加锁和解锁,通过代码来控制锁的获取和释放。
    • Java库实现,功能强大,灵活性高。

5. ThreadLocal有什么用?

答:ThreadLocal在Java中用于创建线程局部变量,确保每个变量都有自己的独立副本变量,从而避免多线程共享一个变量带来的线程安全问题,同时提高了并发性能。然而在使用ThreadLocal时需要注意内存泄露问题,由于ThreadLocal变量的生命周期与线程线相同,如果线程池中线程长时间不被销毁,而ThreadLocal变量没有被正确移除,可能会导致内存泄露问题,因此建议在不再使用ThreadLocal变量时显示调用remove()方法。
它的使用场景有用户会话管理、数据库连接管理、事物管理等。

6. 线程池有什么用?为什么不推荐使用内置线程池?

答:Java中的线程池是一种基于池化技术设计用于执行异步任务的框架,它维护了一定数量的线程,避免频繁地创建和销毁线程带来的性能开销和资源浪费。他的主要作用是提高资源复用、提高系统稳定性、便于管理和提供灵活的并发策略。

不推荐使用内置线程池的主要原因是内置线程池等配置选项有限、不能满足所有的应用场景的需求。Java库中提供了几种预定义线程的实现,如Executors类中的newFixedThreadPoolnewCacheThreadPoolnewSingleThreadExecutor等。

  • FixedThreadPool 和 SingleThreadExecutor 使用无界队列(LinkedBlockingQueue),在任务提交速度超过执行速度时,任务队列可能无限增长,导致内存耗尽。
  • CacheThreadPool 使用无界线程池,在高并发下可能会创建大量线程、导致系统资源耗尽。未被回收的线程可能会长时间占用资源,造成线程泄漏问题。

7. 如何自定义线程池?

答:在Java中,自定义线程池可以通过ThreadPoolExecutor类来实现。ThreadPoolExecutor提供了丰富的配置选项,如核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、任务队列(workQueue)和拒绝策略(rejectedExecutionHandler)等,可以根据具体需求进行灵活配置。

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  public class CustomThreadPoolExample {  public static void main(String[] args) {  // 核心线程数 :线程池在空闲时保留的线程数,即使没有任务需要处理。int corePoolSize = 5;  // 最大线程数 :线程池允许创建的最大线程数。int maximumPoolSize = 10;  // 非核心线程空闲存活时间 :当线程数超过核心线程数时,多余的空闲线程的存活时间。long keepAliveTime = 1L;  // 时间单位 : 空闲线程存活时间的时间单位。TimeUnit unit = TimeUnit.SECONDS;  // 任务队列 :用于保存等待执行任务的队列。ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);  // 线程工厂 : 用于创建新线程。ThreadFactory threadFactory = Executors.defaultThreadFactory();  // 拒绝策略 : 当线程池和队列都满时,如何处理新任务RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();  // 创建ThreadPoolExecutor  ThreadPoolExecutor executor = new ThreadPoolExecutor(  corePoolSize,  maximumPoolSize,  keepAliveTime,  unit,  workQueue,  threadFactory,  handler  );  // 提交任务  for (int i = 0; i < 15; i++) {  int taskId = i;  executor.execute(() -> {  System.out.println(Thread.currentThread().getName() + " is processing " + taskId);  try {  // 模拟任务执行时间  Thread.sleep(1000);  } catch (InterruptedException e) {  Thread.currentThread().interrupt();  }  });  }  // 关闭线程池(不再接受新任务,但已提交的任务会继续执行)  executor.shutdown();  // 等待所有任务完成  while (!executor.isTerminated()) {  // 等待一段时间  }  System.out.println("All tasks completed.");  }  
}

Java_143">8. Java线程池有哪些参数?阻塞队列有几种?拒绝策略有几种?

答:
线程池的参数

  • corePoolSize(核心线程数):线程池中始终保留的线程数量,即使这些线程处于空闲状态。
  • maximumPoolSize(最大线程数):线程池中允许创建的最大线程数量。当任务队列已满且当前线程数小于最大线程数时,会创建新线程来处理任务。
  • keepAliveTime(空闲线程存活时间):当线程池中的线程数超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间。
  • timeUnit(时间单位):keepAliveTime 参数的时间单位。常见值包括 TimeUnit.SECONDS、TimeUnit.MILLISECONDS 等。
  • workQueue(任务队列):用于保存等待执行任务的队列l。
  • threadFactory(线程工厂):用于创建新线程。
  • rejectedExecutionHandler(拒绝策略):当线程池和队列都满时,如何处理新任务。

阻塞队列

  • ArrayBlockingQueue:一个基于数组的有界阻塞队列。按FIFO(先进先出)顺序保存任务。
  • LinkedBlockingQueue:一个基于链表的可选有界阻塞队列。通常用于无限制的任务队列。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等待一个相应的移除操作。
  • PriorityBlockingQueue:一个基于优先级的无限阻塞队列。任务按照优先级顺序执行。
  • DelayQueue:一个基于优先级队列的无界阻塞队列,只有在延迟期满时才能从队列中取走元素。

拒绝策略

  • AbortPolicy(默认策略):抛出 RejectedExecutionException 异常,阻止系统正常工作。
  • CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。这种策略会降低新任务的提交速度,从而减轻线程池的负载。
  • DiscardPolicy:直接丢弃任务,不抛出异常。如果允许任务丢失,这种策略可以用于避免系统过载。
  • DiscardOldestPolicy:丢弃队列中最旧的任务(即即将执行的任务),然后重新尝试提交新任务。

9. 线程池处理任务的流程了解吗?

答:线程池处理任务的流程可以总结为:提交任务、检查核心线程数、任务队列处理、非核心线程创建、执行任务以及线程回收。

  1. 通过submit()或execute()方法提交任务
  2. 检查核心线程数,如果当前线程数少于核心线程数,那么就创建新的核心线程来执行任务。如果当前线程数已达到核心线程数,将任务放入任务队列。
  3. 如果任务队列没有满,将任务队列继续放入队列。如果任务队列已满,检查当前线程数是否小于最大线程数。
  4. 如果当前线程数小于最大线程数,创建新的非核心线程执行任务,如果当前线程已经达到了最大线程数,执行拒绝策略。
  5. 线程从任务队列取出任务执行。
  6. 线程池中的非核心线程在完成任务后不会立即销毁,进入保持存活状态,只有当这些线程在空闲时间超过keepAliveTime后被回收。

10. 如何给线程池命名?为什么建议给线程池命名?

答:要给线程池中的线程命名,可以自定义一个 ThreadFactory,在创建线程时设置线程名称。

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;/*** 线程工厂,它设置线程名称,有利于我们定位问题。*/
public final class NamingThreadFactory implements ThreadFactory {private final AtomicInteger threadNum = new AtomicInteger();private final String name;/*** 创建一个带名字的线程池生产工厂*/public NamingThreadFactory(String name) {this.name = name;}@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setName(name + " [#" + threadNum.incrementAndGet() + "]");return t;}
}

Java 中,为线程池中的线程命名是一种良好的实践。命名线程有助于调试和监控,使得可以轻松识别和跟踪线程的行为和状态。

参考资料


http://www.ppmy.cn/ops/55014.html

相关文章

RStudio学习笔记(一)

R语言是一款适合进行数据分析、统计建模和可视化的编程语言&#xff0c;在统计分析上有独特的优势。 在R语言的学习过程中需要注意一些东西&#xff1a; ① &#xff08;&#xff09;、[ ]、{}三个括号的使用&#xff1b; ②关键字是不需要加引号的&#xff0c;但是字符型数据…

IT专业入门,高考假期预习指南

高考假期是一个很好的预习IT专业的时间。以下是一些建议&#xff0c;可以帮助你在高考假期中对IT专业进行入门预习&#xff1a; - 了解计算机学科&#xff1a;通过阅读相关书籍或观看相关视频&#xff0c;对计算机科学有一个整体的了解&#xff0c;包括计算机的历史、发展、应…

C++中析构函数的作用和用法

在C中&#xff0c;析构函数&#xff08;Destructor&#xff09;是一种特殊的成员函数&#xff0c;它在对象被销毁时自动调用。析构函数的主要作用是释放对象占用的资源&#xff0c;确保程序不会出现资源泄漏。 析构函数的特点 名称&#xff1a;析构函数的名称与类名相同&…

数学建模 —— MATLAB中的矩阵(上)

目录 矩阵的创建方法 (1)直接输入法 (2)函数创建法 (3)导入本地文件中的数据 矩阵元素的引用 矩阵元素的修改和删除 矩阵的创建方法 在MATLAB中&#xff0c;矩阵的创建方法主要有三种&#xff0c;分别是&#xff1a;直接输入法、函数创建法和导入本地文件中的数据。 (1)直…

Unity分享一个简单的3D角色漫游脚本

1.新建一个场景&#xff0c;并创建一脚本 2.给场景中的地面添加一个Ground标签 3.给刚刚新建的脚本编写代码 using UnityEngine;public class PlayerMovement : MonoBehaviour {public float moveSpeed 5f; // 移动速度public float jumpForce 5f; // 跳跃力量public float …

Java中的Monad设计模式及其实现

Java中的Monad设计模式及其实现 在函数式编程中&#xff0c;Monad是一种重要的设计模式&#xff0c;用于处理包含隐含计算信息&#xff08;如计算顺序、环境、状态、错误处理等&#xff09;的计算。Monad提供了一种结构&#xff0c;使得可以将计算链式连接起来&#xff0c;每一…

鸿蒙学习(二)

文章目录 1、弹窗2、走马灯&#xff08;实现轮播图效果&#xff09;3、注解6、多选框、单选框7、Stack8、TextTimer9、DatePicker 1、弹窗 显示提示信息或者用于用户交互 导入模块 prompt 接口 showToast:显示toast showDialog:显示对话框 showContextMenu:显示上下文菜单 sh…

java基于微信小程序+mysql+RocketMQ开发的医院智能问诊系统源码 智能导诊系统 智能导诊小程序源码

java基于微信小程序mysqlRocketMQ开发的医院智能问诊系统源码 智能导诊系统 智能导诊小程序源码 医院导诊系统是一种基于互联网和定位技术的智能化服务系统&#xff0c;旨在为患者提供精准、便捷的医院内部导航和医疗就诊咨询服务。该系统整合了医院的各种医疗服务资源&#x…