基本数据类型和包装类的区别⭐⭐⭐⭐⭐
基本数据类型
Java 中的基本数据类型(也称为原始数据类型)有 8 种:
- byte: 8 位,有符号整数,范围 -128 到 127。
- short: 16 位,有符号整数,范围 -32,768 到 32,767。
- int: 32 位,有符号整数,范围 -2^31 到 2^31-1。
- long: 64 位,有符号整数,范围 -2^63 到 2^63-1。
- float: 32 位,单精度浮点数。
- double: 64 位,双精度浮点数。
- char: 16 位,Unicode 字符,范围 0 到 65,535。
- boolean: 表示 true 或 false。
包装类
Java 为每种基本数据类型提供了对应的包装类,这些包装类位于java.lang包中:
- Byte对应byte
- Short对应short
- Integer对应int
- Long对应long
- Float对应float
- Double对应double
- Character对应char
- Boolean对应boolean
65. 什么是fail-fast机制?⭐
在Java集合框架中,fail-fast是一种机制,用于检测在遍历集合时的结构性修改,并立即抛出异常以防止不一致状态。fail-fast迭代器在检测到集合在迭代过程中被修改后,会抛出ConcurrentModificationException异常。
工作原理
fail-fast迭代器通过在遍历集合时维护一个修改计数器(modification count)来工作。每当集合结构发生变化(如添加或删除元素)时,这个计数器就会增加。当创建迭代器时,它会保存当前的修改计数器值。在每次调用next()方法时,迭代器会检查当前的修改计数器值是否与保存的值一致。如果不一致,说明集合在迭代过程中被修改了,迭代器会立即抛出ConcurrentModificationException。
代码 Demo
登录后复制
-
import java.util.*;
-
public class FailFastExample {
-
public static void main(String[] args) {
-
List<String> list = new ArrayList<>();
-
list.add("A");
-
list.add("B");
-
list.add("C");
-
Iterator<String> iterator = list.iterator();
-
while (iterator.hasNext()) {
-
String element = iterator.next();
-
System.out.println(element);
-
// 在迭代过程中修改集合
-
if (element.equals("B")) {
-
list.add("D"); // 这将引发 ConcurrentModificationException
-
}
-
}
-
}
-
}
AI写代码java运行
在上面的代码中,当迭代器遍历到元素 “B” 时,集合被修改(添加了新元素 “D”),因此迭代器将抛出ConcurrentModificationException。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可】免费获取
注意事项
快速失败并不保证:fail-fast机制并不能保证在所有情况下都能检测到并发修改。它是尽力而为的检测机制,不能依赖于它来实现并发安全。如果需要并发安全的集合,可以使用java.util.concurrent包中的并发集合类。
避免并发修改:在遍历集合时,避免在外部修改集合。可以使用迭代器的remove方法来安全地移除元素。
使用remove方法
为了避免ConcurrentModificationException,可以使用迭代器的remove方法来移除元素:
登录后复制
-
import java.util.*;
-
public class SafeRemovalExample {
-
public static void main(String[] args) {
-
List<String> list = new ArrayList<>();
-
list.add("A");
-
list.add("B");
-
list.add("C");
-
Iterator<String> iterator = list.iterator();
-
while (iterator.hasNext()) {
-
String element = iterator.next();
-
System.out.println(element);
-
// 使用迭代器的 remove 方法安全地移除元素
-
if (element.equals("B")) {
-
iterator.remove();
-
}
-
}
-
System.out.println("After removal: " + list);
-
}
-
}
AI写代码java运行
在这个示例中,使用iterator.remove()方法安全地移除了元素 “B”。
66. 什么是fail-safe机制?⭐
fail-safe机制是与fail-fast机制相对的一种并发处理机制。在 Java 集合框架中,fail-safe迭代器在检测到集合在遍历过程中被修改时,不会抛出异常,而是允许这种修改继续进行。fail-safe迭代器通常是通过在遍历时使用集合的副本来实现的,这样即使原集合被修改,迭代器也不会受到影响。
工作原理
fail-safe迭代器在遍历集合时,实际上是遍历集合的一个副本。因此,任何对原集合的修改都不会影响到迭代器正在遍历的副本。这种机制保证了遍历操作的安全性,但也意味着迭代器不能反映集合的实时变化。
代码 Demo
登录后复制
-
import java.util.concurrent.CopyOnWriteArrayList;
-
import java.util.Iterator;
-
public class FailSafeExample {
-
publicstaticvoidmain(String[] args) {
-
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
-
list.add("A");
-
list.add("B");
-
list.add("C");
-
Iterator<String> iterator = list.iterator();
-
while (iterator.hasNext()) {
-
Stringelement= iterator.next();
-
System.out.println(element);
-
// 在迭代过程中修改集合
-
if (element.equals("B")) {
-
list.add("D"); // 不会引发 ConcurrentModificationException
-
}
-
}
-
System.out.println("After modification: " + list);
-
}
-
}
AI写代码java运行
在上面的代码中,使用CopyOnWriteArrayList作为集合。CopyOnWriteArrayList是一个典型的fail-safe集合类,它在每次修改时都会创建集合的一个副本,因此迭代器不会检测到并发修改,不会抛出ConcurrentModificationException。
主要特点
登录后复制
-
1. **不抛异常**:fail-safe迭代器在检测到集合被修改时,不会抛出ConcurrentModificationException异常。
-
2. **副本遍历**:fail-safe迭代器遍历的是集合的一个副本,而不是原集合。这意味着对原集合的修改不会影响迭代器的遍历。
-
3. **线程安全**:fail-safe集合类(如CopyOnWriteArrayList、ConcurrentHashMap等)通常是线程安全的,适用于并发环境。
AI写代码html
常见的fail-safe集合类
CopyOnWriteArrayList,ConcurrentHashMap,ConcurrentLinkedQueue,ConcurrentSkipListMap,ConcurrentSkipListSet
注意事项
登录后复制
-
1. **性能开销**:由于fail-safe机制通常需要创建集合的副本,因此在修改频繁的场景下,性能开销较大。适用于读多写少的场景。
-
2. **一致性问题**:由于迭代器遍历的是集合的副本,因此它不能反映集合的实时变化。如果需要实时一致性,fail-safe机制可能不适用。
AI写代码html
67. BlockingQueue是什么?⭐
BlockingQueue是 Java 中定义在java.util.concurrent包下的一个接口,它扩展了Queue接口,并添加了阻塞操作。BlockingQueue提供了一种线程安全的机制,用于在多线程环境中处理生产者-消费者问题。
特点和功能
阻塞操作:BlockingQueue提供了阻塞的put和take方法:
put(E e):如果队列已满,则阻塞直到有空间可插入元素。
take():如果队列为空,则阻塞直到有元素可取。
线程安全:所有方法都使用内部锁或其他同步机制来确保线程安全。
多种实现:BlockingQueue有多种实现方式,适用于不同的场景:
ArrayBlockingQueue:基于数组的有界阻塞队列。
LinkedBlockingQueue:基于链表的可选有界阻塞队列。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue:支持延迟元素的无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待一个对应的移除操作。
LinkedTransferQueue:基于链表的无界阻塞队列,支持传输操作。
代码 Demo
如何使用BlockingQueue实现生产者-消费者模式:
登录后复制
-
import java.util.concurrent.BlockingQueue;
-
import java.util.concurrent.LinkedBlockingQueue;
-
public class BlockingQueueExample {
-
public static void main(String[] args) {
-
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);
-
// 生产者线程
-
Thread producer = new Thread(() -> {
-
try {
-
for (int i = 0; i < 10; i++) {
-
System.out.println("Producing: " + i);
-
queue.put(i); // 如果队列已满,阻塞
-
Thread.sleep(100); // 模拟生产时间
-
}
-
} catch (InterruptedException e) {
-
Thread.currentThread().interrupt();
-
}
-
});
-
// 消费者线程
-
Thread consumer = new Thread(() -> {
-
try {
-
for (int i = 0; i < 10; i++) {
-
Integer value = queue.take(); // 如果队列为空,阻塞
-
System.out.println("Consuming: " + value);
-
Thread.sleep(150); // 模拟消费时间
-
}
-
} catch (InterruptedException e) {
-
Thread.currentThread().interrupt();
-
}
-
});
-
producer.start();
-
consumer.start();
-
}
-
}
AI写代码java运行
LinkedBlockingQueue被用作BlockingQueue的实现。生产者线程不断地向队列中添加元素,而消费者线程不断地从队列中取出元素。如果队列已满,生产者线程会阻塞,直到有空间可插入元素;如果队列为空,消费者线程会阻塞,直到有元素可取。
主要方法
BlockingQueue提供了一些常用的方法,这些方法分为四类:
- 抛出异常:
add(E e):如果队列已满,抛出IllegalStateException。
remove():如果队列为空,抛出NoSuchElementException。
element():如果队列为空,抛出NoSuchElementException。
- 返回特殊值:
offer(E e):如果队列已满,返回false。
poll():如果队列为空,返回null。
peek():如果队列为空,返回null。
- 阻塞操作:
put(E e):如果队列已满,阻塞直到有空间可插入元素。
take():如果队列为空,阻塞直到有元素可取。
- 超时操作:
offer(E e, long timeout, TimeUnit unit):在指定的时间内插入元素,如果队列已满,等待直到超时或插入成功。
poll(long timeout, TimeUnit unit):在指定的时间内取出元素,如果队列为空,等待直到超时或取出成功。
68. Java提供了哪些队列?⭐
LinkedList
基于链表实现的双向链表,实现了List、Deque和Queue接口,支持在头部和尾部进行快速插入和删除操作。
使用场景:
需要频繁插入和删除元素的场景。
需要双端队列(Deque)功能的场景,如在头部和尾部进行操作。
PriorityQueue
基于优先级堆(Priority Heap)实现的无界队列。元素按照自然顺序或指定的比较器顺序排列。不允许插入null元素。
使用场景:
需要按优先级处理元素的场景,如任务调度、事件处理等。
需要动态调整元素顺序的场景。
ArrayDeque
基于数组实现的双端队列(Deque),没有容量限制,可以动态扩展,比LinkedList更高效,尤其是在栈和队列操作方面。
使用场景:
需要高效的栈或队列操作的场景。
需要双端队列功能,但不需要线程安全的场景。
ConcurrentLinkedQueue
基于链表实现的无界非阻塞队列。使用无锁算法,提供高效的并发性能。线程安全,适用于高并发环境。
使用场景:
高并发环境下的无界队列。
需要高效的非阻塞并发操作的场景。
LinkedBlockingQueue
基于链表实现的可选有界阻塞队列,支持阻塞的put和take操作,线程安全,适用于生产者-消费者模式。
使用场景:
生产者-消费者模式,特别是在需要限制队列大小的场景。需要线程安全的阻塞队列。
ArrayBlockingQueue
基于数组实现的有界阻塞队列,必须指定容量,支持阻塞的put和take操作。线程安全,适用于生产者-消费者模式。
使用场景:
生产者-消费者模式,特别是在需要固定大小的队列时。需要线程安全的有界阻塞队列。
DelayQueue
支持延迟元素的无界阻塞队列,元素只有在其延迟时间到期后才能被取出。线程安全,适用于并发环境。
使用场景:
需要延迟处理元素的场景,如任务调度、缓存过期处理等。
定时任务执行场景。
LinkedBlockingDeque
基于链表实现的可选有界阻塞双端队列,支持阻塞的put和take操作。线程安全,适用于生产者-消费者模式。
使用场景:生产者-消费者模式,特别是在需要限制队列大小的双端队列场景。需要线程安全的阻塞双端队列。
69. 阻塞队列原理?⭐
阻塞队列是一种线程安全的队列,它在插入和删除操作上可以阻塞线程,以实现生产者-消费者模式等并发编程需求。阻塞队列的核心原理包括锁机制和条件变量。
基本原理
锁机制
阻塞队列使用锁(如ReentrantLock)来确保线程安全。锁保证了同一时间只有一个线程可以执行插入或删除操作,从而避免并发问题。
条件变量
阻塞队列使用条件变量(Condition)来管理线程的等待和通知。条件变量是与锁关联的,可以在特定条件下阻塞线程并在条件满足时唤醒线程。例如,notEmpty和notFull是常见的条件变量,分别用于表示队列是否为空和是否已满。
等待和通知机制:
当线程试图执行插入操作而队列已满时,它会在notFull条件变量上等待,直到队列中有空闲空间。
当线程试图执行删除操作而队列为空时,它会在notEmpty条件变量上等待,直到队列中有可用的元素。
当插入或删除操作成功后,相应的条件变量会被通知(唤醒),以便其他等待的线程可以继续执行。
具体实现 Demo
LinkedBlockingQueue是一个基于链表实现的可选有界阻塞队列。它的基本原理如下:
内部结构:使用一个链表来存储元素。使用两个锁:takeLock和putLock,分别用于控制删除和插入操作。使用两个条件变量:notEmpty和notFull,分别用于表示队列是否为空和是否已满。
插入操作(put):
登录后复制
-
public void put(E e) throws InterruptedException {
-
if (e == null) throw new NullPointerException();
-
int c = -1;
-
Node<E> node = new Node<E>(e);
-
final ReentrantLock putLock = this.putLock;
-
final AtomicInteger count = this.count;
-
putLock.lockInterruptibly();
-
try {
-
while (count.get() == capacity) {
-
notFull.await();
-
}
-
enqueue(node);
-
c = count.getAndIncrement();
-
if (c + 1 < capacity) {
-
notFull.signal();
-
}
-
} finally {
-
putLock.unlock();
-
}
-
if (c == 0) {
-
signalNotEmpty();
-
}
-
}
AI写代码java运行
删除操作(take):
登录后复制
-
public E take() throws InterruptedException {
-
E x;
-
int c = -1;
-
final AtomicInteger count = this.count;
-
final ReentrantLock takeLock = this.takeLock;
-
takeLock.lockInterruptibly();
-
try {
-
while (count.get() == 0) {
-
notEmpty.await();
-
}
-
x = dequeue();
-
c = count.getAndDecrement();
-
if (c > 1) {
-
notEmpty.signal();
-
}
-
} finally {
-
takeLock.unlock();
-
}
-
if (c == capacity) {
-
signalNotFull();
-
}
-
return x;
-
}
AI写代码java运行
等待和通知:
在插入操作中,如果队列已满,线程会在notFull条件变量上等待。
在删除操作中,如果队列为空,线程会在notEmpty条件变量上等待。
插入或删除操作成功后,会相应地通知等待的线程。
70. 重载和重写的区别⭐⭐⭐⭐⭐
重载(Overloading)和重写(Overriding)是面向对象编程中两个重要的概念,它们在方法定义和调用时有不同的用途和规则。
重载
在同一个类中,方法名称相同,但参数列表(参数的类型、数量或顺序)不同的多个方法。
方法名称:相同。
参数列表:必须不同(参数的类型、数量、或顺序)。
返回类型:可以相同也可以不同。
访问修饰符:可以相同也可以不同。
静态/实例方法:都可以重载。
编译时决定:方法的选择在编译时由编译器根据参数列表决定。
重写(Overriding)
在子类中定义一个方法,该方法与父类中的某个方法具有相同的方法名称、参数列表和返回类型,以便在子类中提供该方法的具体实现。
方法名称:相同。
参数列表:必须相同。
返回类型:必须相同(Java 5 及以后可以是协变返回类型,即返回类型可以是父类方法返回类型的子类型)。
访问修饰符:访问级别不能比父类方法更严格(可以更宽松)。
静态/实例方法:只能重写实例方法,不能重写静态方法。
运行时决定:方法的选择在运行时由 JVM 根据对象的实际类型决定(动态绑定)。
总结
重载:发生在同一个类中。方法名称相同,参数列表不同。编译时决定调用哪个方法(静态绑定)。
重写:发生在子类和父类之间。方法名称、参数列表和返回类型必须相同(或协变返回类型)。运行时决定调用哪个方法(动态绑定)。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可】免费获取
71. 为什么要使用扰动函数?⭐
扰动函数的目的是为了提高哈希码的质量,使其在哈希表中更均匀地分布。具体来说:
减少哈希冲突:通过将高位和低位混合,扰动函数减少了哈希码的模式性,降低了哈希冲突的概率。
均匀分布:扰动后的哈希码更加均匀地分布在哈希表的桶中,从而提高了哈希表的性能。
示例 Demo
假设我们有一个键对象,其hashCode()返回值为123456。我们可以通过哈希函数计算其哈希值:
- 调用hashCode()方法:
Plain Text int h = 123456;
- 扰动函数计算:
Plain Text int hash = h ^ (h >>> 16);
- 具体计算步骤:
○ h >>> 16 = 123456 >>> 16 = 1(右移 16 位)
○ hash = 123456 ^ 1 = 123457(异或运算)
最终,哈希值为123457。
72. 进程、线程、管程、协程区别?⭐⭐⭐
进程 (Process)
进程是操作系统分配资源的基本单位。每个进程都有自己的内存空间、文件描述符、堆栈等资源。
进程的特点
独立性:进程之间是独立的,互不干扰。一个进程的崩溃不会影响其他进程。
资源丰富:每个进程拥有独立的资源,包括内存、文件句柄等。
开销大:创建和销毁进程的开销较大,进程间通信(IPC)也相对复杂。
上下文切换:进程的上下文切换开销较大,因为需要切换内存空间和资源。
使用场景
适用于需要强隔离和独立资源的场景,如独立的服务、应用程序等。
线程 (Thread)
线程是进程内的执行单元,一个进程可以包含多个线程。线程共享进程的资源(如内存空间、文件描述符)。
线程的特点
共享资源:同一进程内的线程共享内存和资源,通信方便。
轻量级:线程的创建和销毁开销较小,上下文切换较快。
并发执行:多线程可以并发执行,提高程序的响应速度和资源利用率。
同步问题:由于共享资源,线程间需要同步机制(如锁)来避免资源竞争和数据不一致。
使用场景
适用于需要并发执行的任务,如多任务处理、并行计算等。
管程 (Monitor)
管程是一种高级的同步机制,用于管理共享资源的并发访问。它将共享资源和访问资源的代码封装在一起,通过条件变量和互斥锁来实现同步。
管程特点
封装性:将共享资源和同步代码封装在一起,提供更高层次的抽象。
互斥访问:通过互斥锁确保同一时刻只有一个线程可以访问共享资源。
条件同步:使用条件变量来协调线程间的执行顺序。
使用场景
适用于需要对共享资源进行复杂同步操作的场景,如操作系统内核、并发数据结构等。
协程 (Coroutine)
协程是一种比线程更轻量级的并发执行单元。协程由程序自身调度,而不是由操作系统内核调度。协程可以在执行过程中主动让出控制权,以便其他协程运行。
协程特点
• 轻量级:协程的创建和切换开销极小,通常在用户态完成。
• 主动让出:协程通过显式的调用(如yield)让出控制权,实现合作式多任务。
• 非抢占式:协程之间的切换是合作式的,不存在抢占问题。
• 栈独立:每个协程有自己的栈,避免了线程间共享栈带来的同步问题。
使用场景
适用于需要大量并发任务且切换频繁的场景,如高并发网络服务器、异步编程等。
虚拟线程 (Virtual Thread)
虚拟线程是一个新概念,特别是在 Java 的 Project Loom 中引入。虚拟线程是一种轻量级线程,由 JVM 管理,旨在简化并发编程并提高并发性能。
特点
轻量级:虚拟线程的创建和销毁开销极小,可以高效地管理数百万个线程。
自动管理:由 JVM 自动调度和管理,不需要开发者显式地管理线程池。
兼容性:与传统的 Java 线程 API 兼容,开发者可以用熟悉的线程模型编写高并发程序。
阻塞操作:虚拟线程可以在阻塞操作(如 I/O 操作)时高效地让出 CPU,而不会浪费资源。
使用场景
适用于高并发应用程序,如高性能服务器、Web 应用等。
73. 用户线程与守护线程区别⭐⭐⭐
用户线程
用户线程是应用程序创建的普通线程,也称为非守护线程。当所有用户线程都结束时,Java 虚拟机 (JVM) 也会退出。
特点
生命周期:用户线程的生命周期由应用程序控制。只要有一个用户线程在运行,JVM 就会继续运行。
重要性:用户线程通常用于执行应用程序的主要任务,例如处理业务逻辑、执行计算等。
关闭 JVM:JVM 只有在所有用户线程都结束后才会退出,即使还有守护线程在运行。
使用场景
适用于需要执行重要任务且不能中途被终止的线程。例如:处理用户请求的线程,执行关键业务逻辑的线程
守护线程 (Daemon Thread)
守护线程是为其他线程提供服务和支持的线程。当所有非守护线程(用户线程)都结束时,JVM 会自动退出,即使守护线程还在运行。
特点
生命周期:守护线程的生命周期依赖于用户线程。当所有用户线程结束时,守护线程也会自动终止。
后台任务:守护线程通常用于执行后台任务,如垃圾回收、日志记录等。
低优先级:守护线程通常优先级较低,因为它们主要为用户线程提供支持。
使用场景
适用于执行后台任务或辅助任务的线程,这些任务不需要在 JVM 退出时完成。例如:JVM 的垃圾回收线程,日志记录线程,监控和统计线程
代码 Demo
登录后复制
-
public class ThreadExample {
-
public static void main(String[] args) {
-
Thread userThread = new Thread(() -> {
-
try {
-
Thread.sleep(5000);
-
System.out.println("User thread finished");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
});
-
Thread daemonThread = new Thread(() -> {
-
while (true) {
-
try {
-
Thread.sleep(1000);
-
System.out.println("Daemon thread running");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
});
-
daemonThread.setDaemon(true);
-
userThread.start();
-
daemonThread.start();
-
System.out.println("Main thread finished");
-
}
-
}
AI写代码java运行
在这个例子中:
userThread是一个用户线程,它会运行 5 秒钟。daemonThread是一个守护线程,它会每秒钟打印一次消息。
当userThread结束后,JVM 会退出,即使daemonThread还在运行。
74. Java线程的创建方式?⭐⭐⭐⭐⭐
继承Thread类
通过继承java.lang.Thread类并重写其run方法来创建线程。
登录后复制
-
public class MyThread extends Thread {
-
@Override
-
public void run() {
-
System.out.println("Thread is running");
-
}
-
public static void main(String[] args) {
-
MyThread thread = new MyThread();
-
thread.start();
-
}
-
}
AI写代码java运行
实现Runnable接口
通过实现java.lang.Runnable接口并将其传递给Thread对象来创建线程。
登录后复制
-
public class MyRunnable implements Runnable {
-
@Override
-
public void run() {
-
System.out.println("Runnable is running");
-
}
-
public static void main(String[] args) {
-
MyRunnable myRunnable = new MyRunnable();
-
Thread thread = new Thread(myRunnable);
-
thread.start();
-
}
-
}
AI写代码java运行
实现Callable接口和使用FutureTask
通过实现java.util.concurrent.Callable接口来创建线程,并使用FutureTask来管理返回结果。
登录后复制
-
import java.util.concurrent.Callable;
-
import java.util.concurrent.ExecutionException;
-
import java.util.concurrent.FutureTask;
-
public class MyCallable implements Callable<String> {
-
@Override
-
public String call() throws Exception {
-
return "Callable result";
-
}
-
public static void main(String[] args) {
-
MyCallable myCallable = new MyCallable();
-
FutureTask<String> futureTask = new FutureTask<>(myCallable);
-
Thread thread = new Thread(futureTask);
-
thread.start();
-
try {
-
System.out.println("Result: " + futureTask.get());
-
} catch (InterruptedException | ExecutionException e) {
-
e.printStackTrace();
-
}
-
}
-
}
AI写代码java运行
使用线程池
通过java.util.concurrent.ExecutorService创建和管理线程池,避免手动创建和管理线程。
登录后复制
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
public class ThreadPoolExample {
-
public static void main(String[] args) {
-
ExecutorService executorService = Executors.newFixedThreadPool(3);
-
for (int i = 0; i < 5; i++) {
-
executorService.execute(() -> {
-
System.out.println("Thread pool task is running");
-
});
-
}
-
executorService.shutdown();
-
}
-
}
AI写代码java运行
使用Lambda表达式 (Java 8及以上)
通过Lambda表达式简化Runnable接口的实现。
登录后复制
-
public class LambdaExample {
-
public static void main(String[] args) {
-
Thread thread = new Thread(() -> System.out.println("Lambda thread is running"));
-
thread.start();
-
}
-
}
AI写代码java运行
75. Java线程的几种创建方式有什么区别?⭐⭐⭐⭐⭐
继承Thread类
通过继承Thread类并重写run方法。适合快速创建简单的线程任务。缺点就是Java不支持多继承,如果你的类已经继承了另一个类,就不能再继承Thread。同时不适合复杂的线程管理和资源共享场景。
实现Runnable接口
通过实现Runnable接口并将其传递给Thread对象。适合需要共享资源或任务的场景。可以实现多个接口,增加了灵活性。多个线程可以共享同一个Runnable实例,方便资源共享和任务分配。
实现Callable接口和使用FutureTask
实现Callable接口来创建线程,并使用FutureTask来管理返回结果。适合需要返回结果的并发任务,可以返回任务执行结果。同时可以抛出异常,便于异常处理。相比Runnable,实现和使用稍微复杂一些。
使用线程池
通过ExecutorService来创建和管理线程池,适合需要管理大量线程的场景。减少了频繁创建和销毁线程的开销。更好地管理系统资源,防止资源耗尽。可以根据任务量动态调整线程池大小。
76. Java多线程优先级是什么?⭐⭐
在Java中,每个线程都有一个优先级,优先级决定了线程调度器对线程的调度顺序。线程的优先级是一个整数值,范围在1到10之间。
最低优先级:Thread.MIN_PRIORITY(值为1)
默认优先级:Thread.NORM_PRIORITY(值为5)
最高优先级:Thread.MAX_PRIORITY(值为10)
线程优先级的作用
线程优先级是对线程调度器的一种建议,调度器会根据优先级来决定哪个线程应该优先执行。然而,线程优先级并不能保证线程一定会按照优先级顺序执行,具体的调度行为依赖于操作系统的线程调度策略。
设置线程优先级
可以通过setPriority(int newPriority)方法来设置线程的优先级。需要注意的是,设置的优先级必须在1到10之间,否则会抛出IllegalArgumentException。
登录后复制
-
public class ThreadPriorityExample {
-
public static void main(String[] args) {
-
Thread lowPriorityThread = new Thread(() -> {
-
for (int i = 0; i < 5; i++) {
-
System.out.println("Low priority thread running");
-
}
-
});
-
lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
-
Thread highPriorityThread = new Thread(() -> {
-
for (int i = 0; i < 5; i++) {
-
System.out.println("High priority thread running");
-
}
-
});
-
highPriorityThread.setPriority(Thread.MAX_PRIORITY);
-
lowPriorityThread.start();
-
highPriorityThread.start();
-
}
-
}
AI写代码java运行
我们创建了两个线程,一个设置为最低优先级,一个设置为最高优先级。通常情况下,系统会优先调度高优先级的线程执行,但这并不是绝对的,具体行为依赖于操作系统的调度策略。
注意事项
登录后复制
-
1. **优先级并不保证执行顺序**:线程优先级只是对线程调度器的一个建议,不能保证线程会按照优先级顺序执行。
-
2. **避免滥用优先级**:不建议过度依赖线程优先级来控制线程的执行顺序,应该更多地通过设计合理的并发控制机制(如锁、信号量、条件变量等)来管理线程。
-
3. **平台依赖性**:不同操作系统对线程优先级的支持和实现方式可能不同,因此在跨平台应用中,优先级的效果可能不一致。
AI写代码html
77. Java多线程的生命周期是什么⭐⭐⭐⭐
在Java中,线程的生命周期包括多个状态,每个状态表示线程在其生命周期中的不同阶段。线程的生命周期状态主要包括:
- 新建(New)
- 就绪(Runnable)
- 运行(Running)
- 阻塞(Blocked)
- 等待(Waiting)
- 超时等待(Timed Waiting)
- 终止(Terminated)
线程状态详解
新建(New)
当一个线程对象被创建时(例如,通过new Thread()),线程处于新建状态。此时,线程还没有开始运行。
就绪(Runnable)
当调用start()方法后,线程进入就绪状态。线程在就绪状态下等待操作系统的线程调度器将其调度到CPU上执行。注意:在Java中,Runnable状态包括了运行状态(Running),即线程可以运行,也可能正在运行。
运行(Running)
当线程获得CPU时间片并开始执行其run()方法时,线程进入运行状态。线程在这个状态下实际执行任务。
阻塞(Blocked)
线程在等待一个监视器锁(monitor lock)时进入阻塞状态。例如,线程试图进入一个synchronized方法或块,但其他线程已经持有了该对象的锁。
等待(Waiting)
线程无限期地等待另一个线程显式地唤醒它时进入等待状态。例如,调用Object.wait()方法,或者Thread.join()方法(不带超时时间),或者LockSupport.park()方法。
超时等待(Timed Waiting)
线程在等待另一个线程显式地唤醒它,或者等待特定的时间段后自动唤醒时进入超时等待状态。例如,调用Thread.sleep(long millis)方法,Object.wait(long timeout)方法,Thread.join(long millis)方法,或者LockSupport.parkNanos(long nanos)方法。
终止(Terminated)
当线程的run()方法执行完毕或者抛出未捕获的异常时,线程进入终止状态。线程在这个状态下不再执行任何任务。
78. 为什么java多线程调用的是start方法不是run方法?⭐⭐⭐⭐
start()方法
start()方法的作用是启动一个新线程,并且使该线程进入就绪状态,等待操作系统的线程调度器来调度它执行。当你调用start()方法时,Java虚拟机会创建一个新的执行线程。在这个新的线程中,Java虚拟机会自动调用run()方法。调用start()方法后,原来的线程和新创建的线程可以并发执行。
run()方法
run()方法包含了线程执行的代码,是你需要在新线程中执行的任务。如果直接调用run()方法,run()方法会在当前线程中执行,而不会启动一个新线程。直接调用run()方法不会创建新的线程,所有代码在调用run()方法的线程中顺序执行。
代码 Demo
登录后复制
-
public class MyThread extends Thread {
-
@Override
-
public void run() {
-
System.out.println("Thread is running");
-
}
-
public static void main(String[] args) {
-
MyThread thread = new MyThread();
-
// 调用 start() 方法
-
thread.start(); // 这会启动一个新线程,run() 方法在新线程中执行
-
// 调用 run() 方法
-
thread.run(); // 这会在当前线程中执行 run() 方法,不会启动新线程
-
}
-
}
AI写代码java运行
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可】免费获取
为什么不能直接调用run()方法
登录后复制
-
1. **启动新线程**:start()方法负责启动一个新线程,而直接调用run()只是普通的方法调用,不会启动新线程。
-
2. **并发执行**:通过start()方法启动的新线程可以与原来的线程并发执行,而直接调用run()方法则是在当前线程中顺序执行。
-
3. **线程状态管理**:start()方法会使线程进入就绪状态,等待操作系统调度,而直接调用run()方法不会改变线程的状态管理。
AI写代码html
79. 线程的基本方法⭐⭐⭐⭐⭐
start()
start()方法用于启动线程。线程创建以后,并不会自动运行,需要我们调用start(),将线程的状态设为就绪状态,但不一定马上就被运行,得等到CPU分配时间片以后,才会运行
登录后复制
-
class MyThread extends Thread {
-
@Override
-
public void run() {
-
System.out.println("Thread is running");
-
}
-
}
-
public class Main {
-
public static void main(String[] args) {
-
MyThread t1 = new MyThread();
-
t1.start(); // 启动新线程
-
}
-
}
AI写代码java运行
注意:直接调用run()方法不会启动新线程,而是在当前线程中执行run()方法。
run()
run()方法包含线程执行的代码。它是Thread类和Runnable接口的核心方法。
登录后复制
-
class MyRunnable implements Runnable {
-
@Override
-
public void run() {
-
System.out.println("Thread is running");
-
}
-
}
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(new MyRunnable());
-
t1.start(); // 启动新线程,实际调用的仍是 run() 方法
-
}
-
}
AI写代码java运行
sleep(long millis)
sleep(long millis)方法使当前线程休眠指定的毫秒数。它会抛出InterruptedException,因此需要处理该异常。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
try {
-
System.out.println("Thread is sleeping");
-
Thread.sleep(1000); // 休眠1秒
-
System.out.println("Thread woke up");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
});
-
t1.start();
-
}
-
}
AI写代码java运行
join()
join()方法等待线程终止。调用该方法的线程会等待被调用线程执行完毕后再继续执行。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
try {
-
Thread.sleep(1000); // 模拟工作
-
System.out.println("Thread finished");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
});
-
t1.start();
-
try {
-
t1.join(); // 等待 t1 线程结束
-
System.out.println("Main thread continues");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
AI写代码java运行
interrupt()
interrupt()方法用于中断线程。被中断的线程会抛出InterruptedException。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
try {
-
while (!Thread.currentThread().isInterrupted()) {
-
System.out.println("Thread is running");
-
Thread.sleep(500);
-
}
-
} catch (InterruptedException e) {
-
System.out.println("Thread was interrupted");
-
}
-
});
-
t1.start();
-
try {
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
t1.interrupt(); // 中断 t1 线程
-
}
-
}
AI写代码java运行
isInterrupted()
isInterrupted()方法用于检查线程是否被中断。它返回一个布尔值。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
while (true) {
-
if (Thread.currentThread().isInterrupted()) {
-
System.out.println("Thread is interrupted");
-
break;
-
}
-
System.out.println("Thread is running");
-
try {
-
Thread.sleep(500);
-
} catch (InterruptedException e) {
-
Thread.currentThread().interrupt(); // 重新设置中断状态
-
}
-
}
-
});
-
t1.start();
-
try {
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
t1.interrupt(); // 中断 t1 线程
-
}
-
}
AI写代码java运行
setPriority(int newPriority)
setPriority(int newPriority)方法用于设置线程的优先级。优先级范围从Thread.MIN_PRIORITY(1) 到Thread.MAX_PRIORITY(10),默认优先级为Thread.NORM_PRIORITY(5)。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
System.out.println("Thread is running with priority: " + Thread.currentThread().getPriority());
-
});
-
t1.setPriority(Thread.MAX_PRIORITY);
-
t1.start();
-
}
-
}
AI写代码java运行
getPriority()
getPriority()方法用于获取线程的优先级。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
System.out.println("Thread priority: " + Thread.currentThread().getPriority());
-
});
-
t1.start();
-
}
-
}
AI写代码java运行
setName(String name)
setName(String name)方法用于设置线程的名称。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
System.out.println("Thread name: " + Thread.currentThread().getName());
-
});
-
t1.setName("MyThread");
-
t1.start();
-
}
-
}
AI写代码java运行
getName()
getName()方法用于获取线程的名称。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
System.out.println("Thread name: " + Thread.currentThread().getName());
-
});
-
t1.start();
-
}
-
}
AI写代码java运行
currentThread()
currentThread()方法用于获取当前正在执行的线程。
登录后复制
-
public class Main {
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
Thread currentThread = Thread.currentThread();
-
System.out.println("Current thread: " + currentThread.getName());
-
});
-
t1.start();
-
}
-
}
AI写代码java运行
80. sleep和wait方法的区别⭐⭐⭐⭐
Thread.sleep()和Object.wait()是Java中用于控制线程行为的两种方法,但它们有着显著的区别。
区别
sleep 方法 | object.wait 方法 | |
---|---|---|
定义 | Thread.sleep(long millis)是一个静态方法,属于Thread类。 | wait()是一个实例方法,属于Object类。 |
作用 | 使当前线程进入休眠状态,暂停执行一段时间(以毫秒为单位)。 | 使当前线程等待,直到另一个线程调用该对象的notify()或notifyAll()方法来唤醒它。 |
锁状态 | 调用sleep方法时,线程不会释放它所持有的任何锁。 | 调用wait方法时,线程必须持有该对象的监视器锁,并且会释放该锁,进入等待状态。 |
唤醒 | 在指定的时间到期后,线程会自动从休眠状态中醒来并继续执行,不需要其他线程显式唤醒它。 | 线程必须被其他线程显式唤醒,调用notify()或notifyAll()方法,或者在超时等待的情况下,时间到期后自动唤醒。 |
异常 | sleep方法会抛出InterruptedException,如果线程在休眠期间被中断。 | wait方法会抛出InterruptedException,如果线程在等待期间被中断。 |
场景 | 通常用于暂停线程的执行,模拟延迟或让出CPU时间片。 | 通常用于线程间通信,协调线程之间的执行顺序。 |
Thread.sleep()示例
登录后复制
-
public class SleepExample {
-
public static void main(String[] args) {
-
Thread thread = new Thread(() -> {
-
try {
-
System.out.println("Thread is going to sleep");
-
Thread.sleep(2000); // 休眠2秒
-
System.out.println("Thread woke up");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
});
-
thread.start();
-
}
-
}
AI写代码java运行
Object.wait()示例
登录后复制
-
public class WaitNotifyExample {
-
private static final Object lock = new Object();
-
public static void main(String[] args) {
-
Thread waitingThread = new Thread(() -> {
-
synchronized (lock) {
-
try {
-
System.out.println("Thread is waiting");
-
lock.wait(); // 进入等待状态,并释放锁
-
System.out.println("Thread is resumed");
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
});
-
Thread notifyingThread = new Thread(() -> {
-
synchronized (lock) {
-
try {
-
Thread.sleep(2000); // 休眠2秒
-
System.out.println("Thread is going to notify");
-
lock.notify(); // 唤醒等待线程
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
});
-
waitingThread.start();
-
notifyingThread.start();
-
}
-
}
AI写代码java运行
81. 介绍一下常用的java的线程池?⭐⭐
FixedThreadPool(固定大小线程池)
线程池中有固定数量的线程。无论有多少任务提交,线程池中的线程数量始终不变。当所有线程都处于忙碌状态时,新的任务将会在队列中等待。适用于负载较均衡的场景,任务数量相对稳定。
Plain Text ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
CachedThreadPool(缓存线程池)
线程池中线程数量不固定,可以根据需要自动创建新线程。如果线程池中的线程在60秒内没有被使用,则会被终止并从池中移除。当提交新任务时,如果没有空闲线程,则会创建新线程。适用于执行很多短期异步任务的小程序,或者负载较轻的服务器。
Plain Text ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
SingleThreadExecutor(单线程池)
线程池中只有一个线程,所有任务按照提交的顺序执行。确保所有任务在同一个线程中按顺序执行。适用于需要保证顺序执行任务的场景。
Plain Text ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
ScheduledThreadPool(定时线程池)
线程池可以在给定延迟后运行任务,或者定期执行任务。类似于Timer类,但更灵活且功能更强大。适用于需要周期性执行任务的场景。
Plain Text ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
WorkStealingPool(工作窃取线程池)
使用多个工作队列减少竞争,适用于并行计算。线程池中的线程数量是Runtime.getRuntime().availableProcessors()的返回值。适用于需要大量并行任务的场景。
Plain Text ExecutorService workStealingPool = Executors.newWorkStealingPool();
82. Java线程池的原理⭐⭐⭐⭐
Java线程池是一种管理和复用线程的机制,旨在提高应用程序的性能和资源利用效率。线程池通过减少线程创建和销毁的开销来提高系统的响应速度和吞吐量,并且可以有效管理和控制线程的数量,防止过多的线程导致系统资源耗尽。
线程池的基本原理
- 线程复用:线程池在初始化时创建一定数量的线程,这些线程在任务执行完毕后不会被销毁,而是被回收并重新用于执行新的任务。
- 任务队列:当所有线程都在忙碌时,新提交的任务会被放入任务队列中,等待空闲线程来执行。
- 线程管理:线程池可以根据需要动态调整线程的数量,创建新线程或销毁空闲线程,以应对任务量的变化。
- 资源控制:通过限制线程的数量,线程池可以防止系统资源(如CPU、内存)被过度消耗。
83. 使用线程池的好处⭐⭐⭐⭐⭐
提高性能和响应速度
减少线程创建和销毁的开销:每次创建和销毁线程都需要消耗系统资源。线程池通过复用线程,减少了这些开销,从而提高了系统的性能和响应速度。
快速响应任务:线程池中已有的线程可以立即执行新任务,而不需要等待新的线程创建完成。
更好的资源管理
控制并发线程的数量:线程池可以限制并发执行的线程数量,防止系统资源(如CPU、内存)被过度消耗,避免由于过多线程导致的资源耗尽。
任务排队:线程池内部维护一个任务队列,当所有线程都在忙碌时,新任务会被放入队列中等待执行,这样可以平滑地处理任务高峰。
简化并发编程
简化线程管理:开发者不需要手动创建、管理和销毁线程,减少了并发编程的复杂性和出错的可能性。
统一的任务提交接口:通过统一的接口(如execute和submit方法)提交任务,简化了任务管理和执行。
提高系统稳定性
避免资源枯竭:通过限制线程数量和任务队列长度,线程池可以防止系统资源被耗尽,从而提高系统的稳定性和可靠性。
拒绝策略:线程池提供了多种拒绝策略(如AbortPolicy、CallerRunsPolicy等),可以灵活处理任务队列已满时的新任务,避免系统崩溃。
更好的性能监控和调优
线程池监控:通过ThreadPoolExecutor提供的监控方法(如getPoolSize、getActiveCount、getCompletedTaskCount等),可以方便地监控线程池的状态和性能。
调优参数:线程池提供了多个参数(如核心线程数、最大线程数、空闲线程存活时间等),可以根据具体应用场景进行调优,以达到最佳性能。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可】免费获取
84. 线程池的核心构造参数有哪些?⭐⭐⭐⭐⭐
构造参数
corePoolSize(核心线程数)
线程池中始终保持运行的最小线程数,即使这些线程处于空闲状态也不会被销毁。当提交一个新任务时,如果当前运行的线程数少于corePoolSize,即使有空闲线程,也会创建一个新线程来处理任务。
maximumPoolSize(最大线程数)
线程池允许创建的最大线程数。当任务队列已满且当前运行的线程数小于maximumPoolSize时,会创建新线程来执行任务。
keepAliveTime(线程空闲时间)
当线程池中的线程数超过corePoolSize时,多余的空闲线程在等待新任务的最大时间。超过这个时间后,这些空闲线程将被终止。
unit(时间单位)
keepAliveTime参数的时间单位。可以是TimeUnit枚举中的任意值,如TimeUnit.SECONDS、TimeUnit.MILLISECONDS等。
workQueue(任务队列)
用于保存等待执行的任务的队列。常用的队列实现包括:
LinkedBlockingQueue:一个基于链表的无界队列。
ArrayBlockingQueue:一个基于数组的有界队列。
SynchronousQueue:一个不存储元素的队列,每个插入操作必须等待一个对应的移除操作。
PriorityBlockingQueue:一个支持优先级排序的无界队列。
threadFactory(线程工厂)
用于创建新线程的工厂。可以通过自定义线程工厂来设置线程的名称、优先级等属性。默认实现是Executors.defaultThreadFactory()。
handler(拒绝策略)
当任务队列已满且线程数量达到最大线程数时,新的任务会被拒绝执行。拒绝策略定义了这种情况下的处理方式。
常用的拒绝策略包括:
AbortPolicy:抛出RejectedExecutionException,默认策略。
CallerRunsPolicy:由调用线程执行任务。
DiscardPolicy:丢弃任务,不抛出异常。
DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新尝试提交新任务。
代码 Demo
暂时无法在飞书文档外展示此内容
参数调优
登录后复制
-
1. **corePoolSize 和 maximumPoolSize**:根据应用的具体需求和系统资源进行设置。对于CPU密集型任务,可以设置较小的核心线程数;对于I/O密集型任务,可以设置较大的核心线程数。
-
2. **keepAliveTime 和 unit**:通常设置为较长的时间,以便在任务高峰期过后,线程池能够回收多余的线程。
-
3. **workQueue**:选择合适的队列类型和大小。对于任务量较大的场景,可以使用无界队列;对于需要限制任务队列长度的场景,可以使用有界队列。
-
4. **threadFactory**:如果需要自定义线程属性(如名称、优先级等),可以实现一个自定义的ThreadFactory。
-
5. **handler**:根据应用的容错需求选择合适的拒绝策略。对于关键任务,可以使用CallerRunsPolicy或自定义拒绝策略。
AI写代码html
85. Java 线程池工作过程?⭐⭐
-
线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
-
当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常 RejectExecutionException。
-
当一个线程完成任务时,它会从队列中取下一个任务来执行。
-
当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
86. 如何重构一个线程工厂⭐⭐
为什么要使用线程工厂
统一管理线程创建:通过集中管理线程的创建过程,可以确保所有线程具有一致的属性设置,如名称、优先级和是否为守护线程。
增强可维护性:将线程创建逻辑从业务代码中分离出来,使代码更清晰、更易维护。
提高可扩展性:通过自定义线程工厂,可以轻松添加新的功能,例如日志记录、异常处理和线程组管理。
基本的线程工厂实现
默认的线程工厂实现(Executors.defaultThreadFactory())创建的线程没有特别的属性设置。可以通过实现ThreadFactory接口来定制线程的创建过程。
- 实现ThreadFactory接口:定义一个类实现ThreadFactory接口,并重写newThread方法。
- 设置线程属性:在newThread方法中,创建新的线程并设置其属性,如名称、优先级和是否为守护线程。
登录后复制
-
import java.util.concurrent.ThreadFactory;
-
import java.util.concurrent.atomic.AtomicInteger;
-
public class CustomThreadFactory implements ThreadFactory {
-
private final AtomicInteger threadNumber = new AtomicInteger(1);
-
private final String namePrefix;
-
private final boolean daemon;
-
private final int priority;
-
public CustomThreadFactory(String namePrefix, boolean daemon, int priority) {
-
this.namePrefix = namePrefix;
-
this.daemon = daemon;
-
this.priority = priority;
-
}
-
@Override
-
public Thread newThread(Runnable r) {
-
Thread thread = new Thread(r, namePrefix + "-thread-" + threadNumber.getAndIncrement());
-
thread.setDaemon(daemon);
-
thread.setPriority(priority);
-
return thread;
-
}
-
}
AI写代码java运行
扩展和优化线程工厂
为了使线程工厂更加强大和灵活,我们可以添加更多功能。例如:
日志记录
记录每个线程的创建过程,便于调试和监控。
登录后复制
-
import java.util.logging.Level;
-
import java.util.logging.Logger;
-
public class EnhancedThreadFactory extends CustomThreadFactory {
-
private static final Logger logger = Logger.getLogger(EnhancedThreadFactory.class.getName());
-
public EnhancedThreadFactory(String namePrefix, boolean daemon, int priority) {
-
super(namePrefix, daemon, priority);
-
}
-
@Override
-
public Thread newThread(Runnable r) {
-
Thread thread = super.newThread(r);
-
logger.log(Level.INFO, "Created new thread: {0}", thread.getName());
-
return thread;
-
}
-
}
AI写代码java运行
线程组管理
将线程归类到特定的线程组中,便于管理和控制。
登录后复制
-
public class GroupedThreadFactory extends CustomThreadFactory {
-
private final ThreadGroup group;
-
public GroupedThreadFactory(String namePrefix, boolean daemon, int priority, ThreadGroup group) {
-
super(namePrefix, daemon, priority);
-
this.group = group;
-
}
-
@Override
-
public Thread newThread(Runnable r) {
-
Thread thread = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement());
-
thread.setDaemon(daemon);
-
thread.setPriority(priority);
-
return thread;
-
}
-
}
AI写代码java运行
使用自定义线程工厂
通过自定义的线程工厂创建线程池,确保线程具有一致的属性设置。
登录后复制
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
public class ThreadFactoryExample {
-
public static void main(String[] args) {
-
CustomThreadFactory threadFactory = new CustomThreadFactory("CustomPool", false, Thread.NORM_PRIORITY);
-
ExecutorService executorService = Executors.newFixedThreadPool(5, threadFactory);
-
for (int i = 0; i < 10; i++) {
-
executorService.submit(() -> {
-
String threadName = Thread.currentThread().getName();
-
System.out.println("Hello from " + threadName);
-
});
-
}
-
executorService.shutdown();
-
}
-
}
AI写代码java运行
87. 线程池的拒绝策略有哪些?⭐⭐
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
Java的java.util.concurrent包中提供了几种内置的拒绝策略,通过实现RejectedExecutionHandler接口来定义它们。以下是几种常见的拒绝策略:
AbortPolicy(默认策略)
抛出RejectedExecutionException异常,通知调用者任务被拒绝。
登录后复制
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
AI写代码java运行
- 1.
CallerRunsPolicy
由调用线程(提交任务的线程)直接运行被拒绝的任务。这种策略提供了一种简单的反馈机制,减缓提交任务的速度。
登录后复制
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
AI写代码java运行
- 1.
DiscardPolicy
直接丢弃被拒绝的任务,不做任何处理,也不抛出异常。
登录后复制
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
AI写代码java运行
- 1.
DiscardOldestPolicy
丢弃最早提交的未处理任务,然后重新尝试执行当前被拒绝的任务。
登录后复制
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();
AI写代码java运行
- 1.
自定义拒绝策略
除了内置的拒绝策略,还可以通过实现RejectedExecutionHandler接口来定义自己的拒绝策略。
登录后复制
-
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
-
@Override
-
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
-
// 自定义的拒绝处理逻辑
-
System.out.println("Task " + r.toString() + " rejected");
-
}
-
}
-
// 使用自定义拒绝策略
-
RejectedExecutionHandler handler = new CustomRejectedExecutionHandler();
AI写代码java运行
使用拒绝策略
在创建ThreadPoolExecutor时,可以将拒绝策略作为参数传入:
登录后复制
-
ThreadPoolExecutor executor = new ThreadPoolExecutor(
-
corePoolSize,
-
maximumPoolSize,
-
keepAliveTime,
-
timeUnit,
-
workQueue,
-
handler // 传入拒绝策略
-
);
AI写代码java运行
代码 Demo
登录后复制
-
import java.util.concurrent.*;
-
public class RejectionPolicyExample {
-
public static void main(String[] args) {
-
int corePoolSize = 2;
-
int maximumPoolSize = 4;
-
long keepAliveTime = 10;
-
TimeUnit timeUnit = TimeUnit.SECONDS;
-
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
-
// 使用AbortPolicy作为拒绝策略
-
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
-
ThreadPoolExecutor executor = new ThreadPoolExecutor(
-
corePoolSize,
-
maximumPoolSize,
-
keepAliveTime,
-
timeUnit,
-
workQueue,
-
handler
-
);
-
for (int i = 0; i < 10; i++) {
-
executor.execute(new Task(i));
-
}
-
executor.shutdown();
-
}
-
static class Task implements Runnable {
-
private final int taskId;
-
Task(int taskId) {
-
this.taskId = taskId;
-
}
-
@Override
-
public void run() {
-
System.out.println("Executing task " + taskId);
-
try {
-
Thread.sleep(1000);
-
} catch (InterruptedException e) {
-
Thread.currentThread().interrupt();
-
}
-
}
-
@Override
-
public String toString() {
-
return "Task " + taskId;
-
}
-
}
-
}
AI写代码java运行
线程池使用AbortPolicy作为拒绝策略。当任务数量超过线程池的处理能力时,将抛出RejectedExecutionException。默认的拒绝策略是AbortPolicy。
88. 线程池的shutDown和shutDownNow的区别⭐⭐
ExecutorService接口中,shutdown()和shutdownNow()是用于关闭线程池的方法。
shutdown()
shutdown()方法会启动线程池的关闭过程。它会停止接受新的任务提交,但会继续执行已经提交的任务(包括正在执行的和已提交但尚未开始执行的任务)。调用shutdown()后,线程池会进入一个平滑的关闭过程,等待所有已提交的任务完成后才会完全终止。
登录后复制
-
ExecutorServiceexecutorService= Executors.newFixedThreadPool(5);
-
// 提交一些任务for (inti=0; i < 10; i++) {
-
executorService.submit(() -> {
-
System.out.println("Task executed by " + Thread.currentThread().getName());
-
});
-
}
-
// 调用shutdown()
-
executorService.shutdown();
AI写代码java运行
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
shutdownNow()
shutdownNow()方法会尝试停止所有正在执行的任务,并返回一个包含尚未开始执行的任务的列表。它会立即停止接收新的任务,并试图中断正在执行的任务。调用shutdownNow()后,线程池会尽快停止所有正在执行的任务,并返回尚未开始执行的任务列表。需要注意的是,无法保证所有正在执行的任务都能被中断。
登录后复制
-
ExecutorServiceexecutorService= Executors.newFixedThreadPool(5);
-
// 提交一些任务for (inti=0; i < 10; i++) {
-
executorService.submit(() -> {
-
System.out.println("Task executed by " + Thread.currentThread().getName());
-
try {
-
Thread.sleep(1000); // 模拟长时间运行的任务
-
} catch (InterruptedException e) {
-
System.out.println("Task interrupted");
-
}
-
});
-
}
-
// 调用shutdownNow()
-
List<Runnable> notExecutedTasks = executorService.shutdownNow();
-
System.out.println("Tasks not executed: " + notExecutedTasks.size());
AI写代码java运行
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可】免费获取
89. Java的后台进程是什么?⭐⭐
在Java中,后台进程通常指的是“守护线程”(Daemon Thread)。守护线程是一种特殊类型的线程,它在后台运行,用于执行一些辅助任务。当所有的非守护线程(即用户线程)都结束时,JVM会自动退出,不管守护线程是否还在运行。
守护线程的特点
- 辅助角色:守护线程通常用于执行一些后台辅助任务,例如垃圾回收、监控等。
- 自动结束:当所有的非守护线程都结束时,JVM会自动退出,即使还有守护线程在运行。
- 设置方法:可以通过调用Thread对象的setDaemon(true)方法将线程设置为守护线程。
创建守护线程
- 创建线程:创建一个普通的线程。
- 设置为守护线程:在启动线程之前调用setDaemon(true)方法将其设置为守护线程。
登录后复制
-
public class DaemonThreadExample {
-
public static void main(String[] args) {
-
Thread daemonThread = new Thread(new Runnable() {
-
@Override
-
public void run() {
-
try {
-
while (true) {
-
System.out.println("Daemon thread is running...");
-
Thread.sleep(1000);
-
}
-
} catch (InterruptedException e) {
-
Thread.currentThread().interrupt();
-
}
-
}
-
});
-
// 将线程设置为守护线程
-
daemonThread.setDaemon(true);
-
// 启动守护线程
-
daemonThread.start();
-
// 主线程睡眠2秒后结束
-
try {
-
Thread.sleep(2000);
-
} catch (InterruptedException e) {
-
Thread.currentThread().interrupt();
-
}
-
System.out.println("Main thread is ending...");
-
}
-
}
AI写代码java运行
在这个例子中:
1、 创建了一个Runnable对象,并将其传递给一个新的Thread对象。
2、 通过调用daemonThread.setDaemon(true)将线程设置为守护线程。
3、 启动守护线程后,主线程睡眠2秒,然后结束。
4、 当主线程结束时,JVM会自动退出,即使守护线程还在运行。
注意事项
必须在启动前设置:必须在调用start()方法之前调用setDaemon(true),否则会抛出IllegalThreadStateException。
守护线程的生命周期:守护线程的生命周期依赖于JVM中其他非守护线程的生命周期。一旦所有非守护线程结束,JVM就会退出,无论守护线程是否还在运行。
不适合重要任务:由于守护线程在JVM退出时不会确保完成其任务,因此不适合用于需要确保完成的关键任务。
90. 多线程的join方法是什么?⭐⭐⭐⭐
join是Thread类中的一个方法,它允许一个线程等待另一个线程的完成。调用join方法的线程将暂停执行,直到被调用join方法的线程完成其执行。使用join方法,可以管理多线程程序的执行流程。
工作原理
当一个线程调用另一个线程的join方法时,当前线程会进入等待状态,直到目标线程完成或指定的等待时间到期。join方法内部是通过wait机制实现的,当目标线程完成时,会调用notifyAll方法唤醒所有等待的线程。
适用场景
线程同步:确保一个线程在另一个线程完成之后再执行。例如,在多线程计算中,主线程需要等待所有子线程完成计算后再汇总结果。
顺序执行:强制线程按特定顺序执行。例如,必须确保某些初始化任务在线程执行之前完成。
代码 Demo
主线程中等待多个子线程完成
登录后复制
-
publicclassJoinExample{
-
publicstaticvoidmain(String[] args) {
-
Threadt1=newThread(() -> {
-
try {
-
Thread.sleep(2000); // 模拟任务
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
System.out.println("Thread t1 completed");
-
});
-
Threadt2=newThread(() -> {
-
try {
-
Thread.sleep(3000); // 模拟任务
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
System.out.println("Thread t2 completed");
-
});
-
t1.start();
-
t2.start();
-
try {
-
t1.join(); // 等待t1完成
-
t2.join(); // 等待t2完成
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
//主线程将等待t1和t2都完成后,才会继续执行并打印最终的消息
-
System.out.println("Main thread completed after t1 and t2");
-
}
-
}
AI写代码java运行
91. 什么是乐观锁?⭐⭐⭐⭐⭐
乐观锁是一种并发控制机制,主要用于解决并发修改问题。与悲观锁不同,乐观锁假设并发冲突的概率较低,因此在操作之前不加锁,而是在操作提交时进行冲突检测。
乐观锁的工作原理
乐观锁通常通过以下两种方式实现:
版本号
1、 每条记录增加一个版本号字段。
2、 在读取记录时,读取其版本号。
3、 在更新记录时,检查当前版本号是否与读取时的版本号一致。
4、 如果一致,则更新记录并将版本号加一。
5、 如果不一致,则说明有其他事务已经更新了该记录,此时需要重新读取并尝试更新。
时间戳
1、 每条记录增加一个时间戳字段。
2、 在读取记录时,读取其时间戳。
3、 在更新记录时,检查当前时间戳是否与读取时的时间戳一致。
4、 如果一致,则更新记录并更新时间戳。
5、 如果不一致,则说明有其他事务已经更新了该记录,此时需要重新读取并尝试更新。
适用场景
读多写少:系统中读操作频繁,但写操作较少。例如,电商系统中的商品查询操作。
低冲突:并发冲突概率较低的场景。例如,用户个人信息修改,每个用户只会修改自己的信息。
优缺点
优点
无锁开销:乐观锁不需要在读取时加锁,避免了锁的开销和潜在的死锁问题。
高并发性能:适用于读多写少的场景,能提高系统的并发性能。
缺点
重试机制:当并发冲突发生时,需要重新读取数据并重试更新,可能会增加系统的复杂度。
不适用高冲突场景:在并发冲突频繁的场景下,重试次数可能较多,反而降低系统性能。
92. 乐观锁的ABA 问题⭐⭐⭐⭐⭐
乐观锁的ABA问题是指在并发环境中,一个变量在某个线程检查和更新之间可能会被其他线程多次修改,但最终值看起来没有变化,导致原线程无法检测到这些修改。这种情况会导致数据不一致和潜在的并发问题。
ABA问题的具体场景
假设有一个变量X,其初始值为A。以下是一个可能的ABA问题场景:
- 线程T1读取变量X,值为A。
- 线程T1准备更新变量X,但在此之前,线程T2将变量X的值从A改为B,然后又改回A。
- 线程T1再次检查变量X,发现其值仍然是A,于是认为变量X没有被修改,继续进行更新操作。
在这种情况下,线程T1无法检测到变量X已经被其他线程修改过,导致数据不一致。
解决ABA问题的方法
增加版本号
通过引入版本号,每次更新变量时同时更新版本号。即使变量值恢复原值,版本号也会变化(版本号只会增加不会减少),从而检测到修改
登录后复制
-
class VersionedValue {
-
int value;
-
int version;
-
public VersionedValue(int value, int version) {
-
this.value = value;
-
this.version = version;
-
}
-
}
-
public boolean compareAndSwap(VersionedValue current, int newValue) {
-
synchronized (this) {
-
if (current.version == this.version) {
-
this.value = newValue;
-
this.version++;
-
return true;
-
}
-
return false;
-
}
-
}
AI写代码java运行
使用Java的AtomicStampedReference类
这是Java并发包中的一个类,它不仅存储了对象的引用,还存储了一个“戳”(stamp),通常是一个版本号或时间戳。每次更新时同时更新戳,从而检测到ABA问题
登录后复制
-
import java.util.concurrent.atomic.AtomicStampedReference;
-
public class ABAExample {
-
private static AtomicStampedReference<Integer> atomicStampedRef =
-
new AtomicStampedReference<>(100, 0);
-
public static void main(String[] args) {
-
Thread t1 = new Thread(() -> {
-
int[] stampHolder = new int[1];
-
Integer value = atomicStampedRef.get(stampHolder);
-
System.out.println("Thread t1 initial value: " + value + ", stamp: " + stampHolder[0]);
-
try {
-
Thread.sleep(1000); // Simulate some work
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
boolean success = atomicStampedRef.compareAndSet(value, value + 1, stampHolder[0], stampHolder[0] + 1);
-
System.out.println("Thread t1 update success: " + success);
-
});
-
Thread t2 = new Thread(() -> {
-
int[] stampHolder = new int[1];
-
Integer value = atomicStampedRef.get(stampHolder);
-
System.out.println("Thread t2 initial value: " + value + ", stamp: " + stampHolder[0]);
-
atomicStampedRef.compareAndSet(value, value + 1, stampHolder[0], stampHolder[0] + 1);
-
atomicStampedRef.compareAndSet(value + 1, value, stampHolder[0] + 1, stampHolder[0] + 2);
-
});
-
t1.start();
-
t2.start();
-
try {
-
t1.join();
-
t2.join();
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
int[] stampHolder = new int[1];
-
System.out.println("Final value: " + atomicStampedRef.get(stampHolder) + ", final stamp: " + stampHolder[0]);
-
}
-
}
AI写代码java运行
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可】免费获取
93. 什么是CAS?⭐⭐⭐⭐⭐
CAS(Compare-And-Swap)是一种原子操作,用于实现无锁并发数据结构和算法。它允许一个变量在检查和更新之间不会被其他线程修改,从而确保操作的原子性。
CAS的工作原理
CAS操作涉及三个操作数:
内存位置(V):需要操作的变量的内存地址。
预期值(E):期望变量的当前值。
新值(N):希望将变量更新为的新值。
CAS操作的步骤如下:
- 读取变量的当前值。
- 比较变量的当前值与预期值(E)。
- 如果当前值等于预期值,则将变量更新为新值(N),并返回true,表示更新成功。
- 如果当前值不等于预期值,则不进行更新,并返回false,表示更新失败。
登录后复制
-
boolean compareAndSwap(V, E, N) {
-
if (V == E) {
-
V = N;
-
return true;
-
} else {
-
return false;
-
}
-
}
AI写代码java运行
CAS的优点
无锁操作:CAS是无锁操作,不需要加锁,从而避免了锁带来的开销和潜在的死锁问题。
高性能:在高并发环境中,CAS操作的性能通常优于加锁机制,因为它减少了线程的阻塞和上下文切换。
原子性:CAS操作是原子的,即使在多线程环境中,也能确保操作的正确性。
CAS的缺点
ABA问题:如前所述,CAS操作可能会遇到ABA问题,即变量在检查和更新之间被其他线程多次修改,但最终值看起来没有变化。可以通过增加版本号或使用AtomicStampedReference来解决。
自旋等待:CAS操作在失败时通常会自旋重试,这可能会导致CPU资源的浪费,尤其是在高冲突场景下。
CAS在Java中的应用
Java提供了一些基于CAS操作的并发类,例如AtomicInteger、AtomicBoolean、AtomicReference等。它们使用CAS操作来实现原子性更新,避免了显式加锁。
登录后复制
-
import java.util.concurrent.atomic.AtomicInteger;
-
//AtomicInteger类使用CAS操作来实现原子性递增操作,确保在多线程环境下操作的正确性。
-
public class CASExample {
-
private AtomicInteger atomicInteger = new AtomicInteger(0);
-
public void increment() {
-
int oldValue, newValue;
-
do {
-
oldValue = atomicInteger.get();
-
newValue = oldValue + 1;
-
} while (!atomicInteger.compareAndSet(oldValue, newValue));
-
}
-
public int getValue() {
-
return atomicInteger.get();
-
}
-
public static void main(String[] args) {
-
CASExample example = new CASExample();
-
example.increment();
-
System.out.println("Value: " + example.getValue());
-
}
-
}
AI写代码java运行