跳到主要内容

java并发和多线程面试题(二)

50. 什么是线程安全的内部类?如何使用它实现线程安全的单例模式?

回答: 线程安全的内部类是指

在类的内部定义一个私有静态内部类,该内部类持有一个外部类的实例,并在静态初始化时创建实例。这样可以保证懒加载的同时实现线程安全。

代码示例:

public class Singleton {
private Singleton() {}

private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
return Holder.INSTANCE;
}
}

51. 什么是工作窃取算法(Work Stealing Algorithm)?

回答: 工作窃取算法是一种用于任务调度的算法,通常在基于任务的并行编程中使用。它允许空闲线程从其他线程的任务队列中窃取任务来执行,以充分利用多核处理器。

52. 什么是ThreadLocalRandom?如何使用它生成随机数?

回答: ThreadLocalRandom是Java 7引入的一个类,用于在多线程环境下生成随机数,它比Random类更适合高并发环境。

代码示例:

import java.util.concurrent.ThreadLocalRandom;

public class RandomExample {
public static void main(String[] args) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int randomNumber = random.nextInt(1, 101); // 生成1到100的随机整数
System.out.println(randomNumber);
}
}

53. 什么是Amdahl's Law?它对并行性有什么启示?

回答: Amdahl's Law是一种用于衡量并行性效果的公式。它表达了在系统中引入并行性后,加速比的上限。它告诉我们,如果某部分程序是串行的,那么无论如何增加处理器数量,整体加速比仍然受限于串行部分的影响。

54. 什么是线程的可见性问题?如何解决可见性问题?

回答: 线程的可见性问题是指当一个线程修改了共享变量的值,其他线程可能无法立即看到这个变化。可以使用volatile关键字、synchronized关键字、Atomic类等方式来解决可见性问题。

55. 什么是ForkJoinPool?如何使用它执行任务?

回答: ForkJoinPool是Java 7引入的一个线程池,专门用于执行任务分解模式。可以使用ForkJoinTask和RecursiveTask来实现任务的分解和执行。

代码示例:

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

public class ForkJoinExample extends RecursiveTask<Integer> {
private final int threshold = 10;
private int[] array;
private int start;
private int end;

public ForkJoinExample(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}

@Override
protected Integer compute() {
if (end - start <= threshold) {
// 执行任务
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
int middle = (start + end) / 2;
ForkJoinExample leftTask = new ForkJoinExample(array, start, middle);
ForkJoinExample rightTask = new ForkJoinExample(array, middle, end);
leftTask.fork();
rightTask.fork();
return leftTask.join() + rightTask.join();
}
}

public static void main(String[] args) {
int[] array = new int[1000];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
ForkJoinPool pool = ForkJoinPool.commonPool();
int result = pool.invoke(new ForkJoinExample(array, 0, array.length));
System.out.println("Sum: " + result);
}
}

56. 什么是阻塞队列(Blocking Queue)?如何使用它实现生产者-消费者模式?

回答: 阻塞队列是一种线程安全的队列,提供了阻塞操作,如在队列为空时等待元素的添加,或在队列满时等待元素的移除。可以使用阻塞队列实现生产者-消费者模式。

代码示例:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

Runnable producer = () -> {
try {
for (int i = 1; i <= 20; i++) {
queue.put(i);
System.out.println("Produced: " + i);
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};

Runnable consumer = () -> {
try {
for (int i = 1; i <= 20; i++) {
int value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep(400);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};

Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);

producerThread.start();
consumerThread.start();
}
}

57. 什么是Thread.interrupt()方法?如何使用它中断线程?

回答: Thread.interrupt()方法用于中断线程。可以在需要中断线程的地方调用该方法,然后在线程的任务中通过Thread.isInterrupted()来检查中断状态并采取相应的操作。

代码示例:

Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
}
});
thread.start();

// 在需要中断线程的地方调用
thread.interrupt();

58. 什么是Java并发包中的StampedLock?如何使用它实现乐观读锁?

回答: StampedLock是Java并发包中引入的一种锁机制,支持读写锁和乐观读锁。可以使用tryOptimisticRead()方法获取乐观读锁,然

后通过validate()方法来验证读锁是否有效。

代码示例:

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
private double x, y;
private final StampedLock lock = new StampedLock();

void move(double deltaX, double deltaY) {
long stamp = lock.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
lock.unlockWrite(stamp);
}
}

double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead();
double currentX = x;
double currentY = y;
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
currentX = x;
currentY = y;
} finally {
lock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}

59. 如何使用Java中的Exchanger来实现两个线程间的数据交换?

回答: Exchanger是Java并发包中的一个同步工具,用于实现两个线程间的数据交换。它通过exchange()方法来交换数据,并在交换完成后继续执行。

代码示例:

import java.util.concurrent.Exchanger;

public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();

Runnable task1 = () -> {
try {
String data = "Hello from Thread 1";
System.out.println("Thread 1 sending: " + data);
String receivedData = exchanger.exchange(data);
System.out.println("Thread 1 received: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
};

Runnable task2 = () -> {
try {
String data = "Hello from Thread 2";
System.out.println("Thread 2 sending: " + data);
String receivedData = exchanger.exchange(data);
System.out.println("Thread 2 received: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
};

Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);

thread1.start();
thread2.start();
}
}

60. 什么是线程的优先级?如何设置线程的优先级?

回答: 线程的优先级是一个整数,用于指定线程在调度时的优先级顺序。可以使用setPriority()方法来设置线程的优先级。

代码示例:

Thread thread1 = new Thread(() -> {
// 任务代码
});
thread1.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级

Thread thread2 = new Thread(() -> {
// 任务代码
});
thread2.setPriority(Thread.MIN_PRIORITY); // 设置最低优先级

61. 什么是CopyOnWrite容器?它在什么情况下比较适用?

回答: CopyOnWrite容器是Java并发包中的一种线程安全容器,它在修改时创建一个新的副本,从而避免了修改和读取的竞争。它在读多写少的场景下比较适用,因为写操作会导致复制整个容器,开销较大。

62. 什么是线程堆栈溢出?如何避免它?

回答: 线程堆栈溢出是指线程的调用栈空间不足以容纳方法调用所需的信息,导致栈溢出错误。可以通过调整虚拟机的栈大小、优化递归方法或者减少方法调用深度来避免。

63. 什么是内存一致性问题?如何使用volatile解决内存一致性问题?

回答: 内存一致性问题是指多线程环境下,由于内存读写操作的不同步,导致共享变量的值在不同线程之间看起来是不一致的。使用volatile关键字可以确保在写入一个volatile变量时,会将变量的值刷新到主内存,并在读取volatile变量时,会从主内存中读取最新值。

64. 什么是ThreadGroup?它有何作用?

回答: ThreadGroup是一个线程组,用于将多个线程组织在一起,方便管理。它可以用来设置线程组的优先级、设置线程组的非捕获异常处理器等。

65. 什么是线程池的拒绝策略?如何自定义线程池的拒绝策略?

回答: 线程池的拒绝策略是指在线程池无法继续接受新任务时,如何处理新提交的任务。常见的拒绝策略有:AbortPolicy(默认,抛出异常)、CallerRunsPolicy(使用调用线程执行任务)、DiscardPolicy(直接丢弃任务)和DiscardOldestPolicy(丢弃队列中最老的任务)。

可以通过实现RejectedExecutionHandler接口来自定义拒绝策略。

代码示例:

import java.util.concurrent.*;

public class CustomThreadPoolExample {
public static void main(String[] args) {
RejectedExecutionHandler customHandler = (r, executor) -> {
System.out.println("Custom rejected: " + r.toString());
};

ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize
5, // maximumPoolSize
1, TimeUnit.SECONDS, // keepAliveTime and unit
new LinkedBlockingQueue<>(10), // workQueue
customHandler // rejectedExecutionHandler
);

for (int i = 1; i <= 10; i++) {
final int taskNum = i;
executor.execute(() -> {
System.out.println("Executing task " + taskNum);
});
}

executor.shutdown();
}
}

66. 如何在多线程环境下实现定时任务?

回答: 可以使用ScheduledExecutorService接口来在多线程环境下实现定时任务。通过schedule()方法可以安排任务在固定延迟或固定周期执行。

代码示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledTaskExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> {
System.out.println("Task executed at: " + System.currentTimeMillis());
};

// 延迟3秒后执行
executor.schedule(task, 3, TimeUnit.SECONDS);

// 初始延迟1秒,然后每隔2秒执行一次
executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);

// 初始延迟1秒,然后等待上一个任务完成后再延迟2秒执行
executor.scheduleWithFixedDelay(task, 1, 2, TimeUnit.SECONDS);
}
}

67. 如何在多线程环境下处理不可中断的任务?

回答: 可以通过捕获InterruptedException异常并在异常处理中继续执行任务,以达到不可中断的效果。

代码示例:

Thread thread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行不可中断的任务
}
} catch (InterruptedException e) {
// 捕获异常并继续执行任务
Thread.currentThread().interrupt();
}
});
thread.start();

// 在需要中断线程的地方调用
thread.interrupt();

68. 如何使用Java中的Phaser实现多阶段并行任务?

回答: Phaser是Java并发包中的一个同步工具,可以用于多阶段并行任务的同步。它可以分阶段同步线程的执行,当每个阶段的任务都完成时,线程才能

继续执行下一个阶段。

代码示例:

import java.util.concurrent.Phaser;

public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser(3); // 需要同步的线程数

Runnable task = () -> {
// 执行任务
phaser.arriveAndAwaitAdvance(); // 等待其他线程到达
};

Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
Thread thread3 = new Thread(task);

thread1.start();
thread2.start();
thread3.start();

phaser.arriveAndAwaitAdvance(); // 等待所有线程完成第一阶段任务

// 执行下一个阶段任务
}
}

69. 什么是线程安全性?如何评估一个类是否是线程安全的?

回答: 线程安全性是指在多线程环境下,对共享资源的访问和修改不会导致数据不一致或产生竞态条件。可以通过以下几个标准来评估一个类是否是线程安全的:

  • 原子性(Atomicity): 方法的执行必须是原子的,要么全部执行完成,要么不执行。

  • 可见性(Visibility): 修改后的值对其他线程必须是可见的,即读取到最新值。

  • 有序性(Ordering): 程序执行的顺序必须与代码的顺序一致。

如果一个类满足以上三个条件,它就可以被认为是线程安全的。

70. 什么是非阻塞算法?如何在多线程环境下使用非阻塞算法?

回答: 非阻塞算法是指在多线程环境下,不使用传统的锁机制,而是使用原子操作等方法来实现对共享资源的访问。它可以避免线程的阻塞和竞争,从而提高并发性能。

在使用非阻塞算法时,通常会使用原子变量、CAS操作、乐观锁等技术来实现线程安全的访问。然而,非阻塞算法也比较复杂,适用于特定场景,需要仔细的设计和测试。

71. 什么是锁消除和锁膨胀?如何避免它们?

回答: 锁消除是指在编译器优化阶段,将无法被其他线程访问的锁给消除掉,从而减少锁的竞争。锁膨胀是指在多线程环境下,锁的竞争激烈时,将轻量级锁升级为重量级锁,以提供更强的同步保护。

可以通过减少锁的作用范围、使用局部变量来避免锁消除,以及优化锁的粒度来避免锁膨胀。

72. 什么是线程的上下文切换?如何减少上下文切换的开销?

回答: 线程的上下文切换是指从一个线程切换到另一个线程的过程,操作系统需要保存当前线程的上下文并加载下一个线程的上下文。上下文切换会消耗时间和资源,影响系统性能。

可以通过减少线程的数量、合理分配CPU时间片、使用无锁编程、使用协程等方式来减少上下文切换的开销。

73. 什么是线程泄漏?如何避免线程泄漏?

回答: 线程泄漏是指在多线程程序中,某个线程被创建后没有被正确关闭,导致该线程的资源无法被释放,最终可能导致系统性能下降。可以通过合理地使用线程池、及时关闭线程、使用try-with-resources来避免线程泄漏。

74. 什么是ThreadLocal的使用场景?有何优缺点?

回答: ThreadLocal是一个线程局部变量,它提供了在每个线程中存储数据的方式。常见的使用场景包括:

  • 在多线程环境下,每个线程需要

拥有自己的独立副本,如数据库连接、Session等。

  • 需要避免使用传递参数的方式来传递数据,从而降低代码的耦合度。

优点包括:

  • 线程安全:每个线程拥有自己的副本,不会出现竞争条件。

  • 简化参数传递:避免了在方法之间传递大量参数。

缺点包括:

  • 内存泄漏:如果不及时清理ThreadLocal中的数据,可能会导致内存泄漏。

  • 可能增加上下文切换:当线程数过多时,ThreadLocal可能会增加上下文切换的开销。

75. 什么是守护线程(Daemon Thread)?如何创建守护线程?

回答: 守护线程是一种在后台运行的线程,当所有非守护线程结束后,守护线程会随着JVM的退出而结束。可以通过调用setDaemon(true)方法将线程设置为守护线程。

代码示例:

Thread daemonThread = new Thread(() -> {
while (true) {
// 执行后台任务
}
});
daemonThread.setDaemon(true);
daemonThread.start();

76. 什么是CAS(Compare and Swap)操作?它如何实现无锁同步?

回答: CAS(Compare and Swap)操作是一种原子操作,用于实现无锁同步。它在多线程环境下用于解决并发访问共享资源的问题,通过比较内存中的值与期望值是否相等,如果相等则将新值写入内存,从而保证原子性。

CAS操作通常由CPU提供的指令实现,例如AtomicInteger、AtomicLong等。

代码示例:

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
private static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count.incrementAndGet();
}
}).start();
}

// 等待所有线程执行完成
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("Count: " + count);
}
}

点我查看更多精彩内容:www.flydean.com关注公众号加我好友
Loading Comments...