(九) Java 多线程解析:常见问题、实际开发挑战与解决方案

在 Java 开发领域,多线程编程是面试中的重点考察内容,同时也是实际项目开发中的关键技能。本文将详细探讨 Java 多线程面试中的常见问题,深入剖析实际开发中可能遇到的挑战,并提供具体的解决方案,帮助你在面试和工作中更好地应对多线程相关问题。

一、常见面试问题详解

1. 什么是线程?线程与进程的区别是什么?

线程与进程的区别

详细解答: 线程是进程中的一个执行单元,负责执行程序的代码。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件句柄等。线程的创建和切换比进程更轻量级,因为线程共享资源,而进程需要独立管理资源。

  • 线程:线程是 CPU 调度的基本单位,它拥有自己的栈空间和程序计数器,但与其他线程共享进程的内存空间和资源。
  • 进程:进程是操作系统分配资源的基本单位,每个进程都有独立的内存空间和系统资源。进程之间的切换开销较大,因为需要切换内存空间和资源。

2. Java 中如何创建线程?

多线程的创建方式

详细解答: 在 Java 中,创建线程主要有以下几种方式:

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 使用线程池
  4. 使用 FutureTask

3. 什么是线程安全?如何保证线程安全?

详细解答: 线程安全是指在多线程环境下,程序的执行结果是可预期的,不会因为线程之间的竞争而导致数据不一致或程序崩溃。保证线程安全的方法包括:

1.同步(synchronized)

public class Counter { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; } }

2.锁(Lock)

public class Counter { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { return count; } }

3.原子变量(Atomic)

public class Counter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); } }

4.线程安全类

public class SafeMap { 
  private ConcurrentHashMap map = new ConcurrentHashMap<>(); 
public void put(String key, String value) { map.put(key, value); } public String get(String key) { return map.get(key); } }

4. 什么是死锁?如何避免死锁?

详细解答: 死锁是指两个或多个线程因竞争资源而永远等待对方释放资源的现象。避免死锁的方法包括:

  1. 避免资源竞争:尽量减少线程对共享资源的竞争。
  2. 资源有序分配:为资源分配一个顺序,线程必须按照顺序获取资源。
  3. 使用超时机制:在获取锁时设置超时时间,避免线程无限等待。
  4. 使用锁检测工具:如 JConsole、JVisualVM 等工具检测死锁并及时处理。

5. 什么是线程池?为什么要使用线程池?

详细解答: 线程池是一种管理线程的机制,它预先创建一组线程,任务提交给线程池后,线程池会分配一个空闲线程来执行任务。使用线程池的好处包括:

  • 减少线程创建和销毁的开销:线程池可以复用已创建的线程,避免频繁创建和销毁线程的性能开销。
  • 控制线程数量:线程池可以限制线程的最大数量,避免线程过多导致系统资源耗尽。
  • 提供任务调度和管理功能:线程池可以对任务进行调度,如定时任务、周期性任务等。

二、实际开发中的问题与解决方案

1. 线程池的合理配置

问题:在实际开发中,如何合理配置线程池的大小?

解决方案: 线程池的大小应根据任务的类型和服务器的硬件资源来配置。一般来说:

  • CPU 密集型任务:线程池大小可以设置为 CPU 核心数 + 1,以充分利用 CPU 资源。
  • IO 密集型任务:线程池大小可以设置为 CPU 核心数的 2 倍或更高,因为 IO 操作通常会阻塞线程,需要更多的线程来弥补阻塞时间。
  • 混合型任务:可以根据实际情况进行调整,通常可以通过性能测试来确定最佳线程池大小。

示例代码

// CPU 密集型任务
int cpuCores = Runtime.getRuntime().availableProcessors();
ExecutorService cpuPool = Executors.newFixedThreadPool(cpuCores + 1);

// IO 密集型任务
ExecutorService ioPool = Executors.newFixedThreadPool(cpuCores * 2);

2. 线程安全类的使用

问题:在实际开发中,如何选择合适的线程安全类?

解决方案

  • ConcurrentHashMap:适用于高并发场景下的键值存储,性能优于 Hashtable。
  • CopyOnWriteArrayList:适用于读多写少的场景,写操作会复制一份新数组,读操作不会阻塞。
  • AtomicInteger/AtomicLong:适用于简单的数值操作,避免使用 synchronized。
  • ReentrantLock:适用于需要更灵活锁操作的场景,如可中断锁、超时锁等。

示例代码

// ConcurrentHashMap
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("key", "value");
String value = map.get("key");

// CopyOnWriteArrayList
CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
list.add("item");
for (String item : list) {
    System.out.println(item);
}

// AtomicInteger
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
int value = counter.get();

// ReentrantLock
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 操作共享资源
} finally {
    lock.unlock();
}

3. 多线程性能优化

问题:在实际开发中,如何优化多线程程序的性能?

解决方案

  • 减少锁的粒度:将大锁拆分为多个小锁,减少锁的竞争。
  • 使用无锁并发:尽量使用原子变量和 CAS 算法,避免使用锁。
  • 线程池优化:合理配置线程池大小,避免线程过多或过少。
  • 异步编程:使用 CompletableFuture 等异步编程模型,提高程序的并发性能。
  • 避免线程阻塞:尽量减少线程的阻塞操作,如 IO 操作、数据库查询等,可以使用异步 IO 或线程池来优化。

示例代码

// 减少锁的粒度
public class FineGrainedLock {
    private final Object[] locks = new Object[10];
    private int[] data = new int[10];

    public void increment(int index) {
        synchronized (locks[index]) {
            data[index]++;
        }
    }

    public int getData(int index) {
        synchronized (locks[index]) {
            return data[index];
        }
    }
}

// 使用无锁并发
public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

// 异步编程
CompletableFuture future = CompletableFuture.runAsync(() -> {
    System.out.println("Async task is running.");
});

future.join();

4. 多线程调试技巧

问题:在实际开发中,如何调试多线程程序?

解决方案

  • 日志记录:在关键位置添加日志,记录线程的状态和操作,方便定位问题。
  • 线程转储:使用 jstack 工具生成线程转储文件,分析线程的堆栈信息。
  • 性能分析工具:使用 JProfiler、JVisualVM 等工具分析线程的性能和资源使用情况。
  • 单元测试:编写多线程的单元测试,模拟多线程环境,确保代码的正确性。
  • 代码审查:通过代码审查发现潜在的线程安全问题,如未同步的共享变量、死锁等。

示例代码

// 日志记录
public class LoggingThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getId() + " is running.");
    }
}

// 线程转储
// 使用 jstack 工具生成线程转储文件
// jstack -l  > thread_dump.txt

5.多线程的设计模式有哪些?

设计模式

6.常见的线程安全类和线程不安全类有哪些?

线程安全、不安全类

7.线程的生命周期详解。

Java 中线程的生命周期

在 Java 中,线程的生命周期可以分为以下五个主要阶段。每个阶段都有其特定的行为和特点,理解这些阶段对于编写高效且稳定的多线程程序至关重要。

1. 新建(New)

当通过 new 关键字创建一个新的线程对象,但尚未启动时,线程处于新建状态。此时,线程还没有被分配任何系统资源,也没有开始执行。这就像一个人刚刚来到世界,还没有开始任何活动。

示例代码:

Thread thread = new Thread(() -> {
    System.out.println("Thread is running.");
});
// 线程此时处于新建状态

2. 就绪(Runnable)

当调用线程的 start() 方法时,线程进入就绪状态。这表示线程已经准备好执行,等待 CPU 的时间片分配。处于就绪状态的线程会被调度程序选择,一旦获得 CPU 时间,就会进入运行状态。这就像一个人已经准备好开始工作,等待被分配任务。

示例代码:

thread.start();
// 线程处于就绪状态,等待 CPU 调度

3. 运行(Running)

当线程获得 CPU 时间片并开始执行时,处于运行状态。这是线程真正执行代码的阶段。运行中的线程可能会因为多种原因(如等待 I/O 操作完成、等待锁释放等)而进入阻塞状态。这就像一个人正在忙碌地完成工作任务。

示例代码:

// 线程开始运行并打印 "Thread is running."

4. 阻塞(Blocked)

线程在运行过程中可能会因为某些原因(如等待 I/O 操作完成、等待锁释放等)而进入阻塞状态。处于阻塞状态的线程无法执行,但系统会继续运行其他线程。这就像一个人在等待资源或权限,暂时无法继续工作。

5. 死亡(Terminated)

当线程的 run() 方法执行完毕,或者因为某些异常而终止时,线程进入死亡状态。此时,线程已经完成了它的任务或因为错误而停止运行。这就像一个人完成了工作或因某种原因停止工作。

除了上述五个基本状态外,Java 的 Thread.State 枚举还定义了六种线程状态,包括:

  • NEW:线程被创建但尚未启动。
  • RUNNABLE:线程正在 JVM 中执行,但可能正在等待操作系统资源,如处理器。
  • BLOCKED:线程等待监视器锁以进入同步块或方法。
  • WAITING:线程无限期地等待另一个线程执行特定的操作。
  • TIMED_WAITING:线程等待另一个线程执行特定的操作,但存在时间限制。
  • TERMINATED:线程已完成执行。

8.多个线程如何保证按照顺序执行?

在 Java 中,多线程的执行顺序通常是不确定的,因为线程的调度是由操作系统负责的,而不是由 Java 程序直接控制的。如果需要保证多个线程按照特定的顺序执行,可以通过以下方法实现:

方法一:线程等待与通知(wait() 和 notify())

通过 wait() 和 notify() 方法可以实现线程之间的同步控制,从而按顺序执行。

public class SyncOrder {
    private static final Object lock = new Object();
    private static boolean ready1 = false;
    private static boolean ready2 = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1开始执行");
                // 标记线程1已完成
                ready1 = true;
                lock.notify(); // 通知线程2开始执行
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                // 等待线程1完成
                while (!ready1) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程2开始执行");
                // 标记线程2已完成
                ready2 = true;
                lock.notify(); // 通知线程3开始执行
            }
        });

        Thread t3 = new Thread(() -> {
            synchronized (lock) {
                // 等待线程2完成
                while (!ready2) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("线程3开始执行");
            }
        });

        t3.start();
        t2.start();
        t1.start();
    }
}

方法二:Join() 方法

join() 方法可以使当前线程等待另一个线程完成之后再继续执行。

public class JoinOrder {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
        });

        Thread t2 = new Thread(() -> {
            try {
                t1.join(); // 等待线程1完成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2开始执行");
        });

        Thread t3 = new Thread(() -> {
            try {
                t2.join(); // 等待线程2完成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3开始执行");
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

方法三:ExecutorService 和 Future

通过 ExecutorService 提交任务,并用 Future 获取任务执行结果,可以按顺序执行任务。

import java.util.concurrent.*;

public class ExecutorOrder {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        Future future1 = executorService.submit(() -> {
            System.out.println("任务1开始执行");
        });

        Future future2 = executorService.submit(() -> {
            try {
                future1.get(); // 等待任务1完成
                System.out.println("任务2开始执行");
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        Future future3 = executorService.submit(() -> {
            try {
                future2.get(); // 等待任务2完成
                System.out.println("任务3开始执行");
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        executorService.shutdown();
    }
}

方法四:使用 Phaser

Phaser 是一个灵活的同步屏障,可以用来控制线程的执行顺序。

import java.util.concurrent.Phaser;

public class PhaserOrder {
    private static final Phaser phaser = new Phaser();

    public static void main(String[] args) {
        phaser.register(); // 主线程注册
        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            phaser.arriveAndDeregister(); // 线程1完成
        });

        Thread t2 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance(); // 等待线程1完成
            System.out.println("线程2开始执行");
            phaser.arriveAndDeregister(); // 线程2完成
        });

        Thread t3 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance(); // 等待线程2完成
            System.out.println("线程3开始执行");
            phaser.arriveAndDeregister(); // 线程3完成
        });

        phaser.register();
        phaser.register();
        phaser.register();
        t3.start();
        t2.start();
        t1.start();
    }
}

方法五:使用 CountDownLatch

CountDownLatch 可以让一个线程等待其他线程完成某个操作。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchOrder {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        CountDownLatch latch1 = new CountDownLatch(1);
        Thread t2 = new Thread(() -> {
            try {
                latch1.await(); // 等待线程1完成
                System.out.println("线程2开始执行");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        CountDownLatch latch2 = new CountDownLatch(1);
        Thread t3 = new Thread(() -> {
            try {
                latch2.await(); // 等待线程2完成
                System.out.println("线程3开始执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        new Thread(() -> {
            try {
                t1.join();
                latch1.countDown(); // 线程1完成
                t2.join();
                latch2.countDown(); // 线程2完成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        t2.start();
        t3.start();
    }
}

方法六:使用线程池按顺序执行

如果线程池的线程数设置为 1,那么提交的任务会按顺序执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolOrder {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(() -> System.out.println("任务1开始执行"));
        executorService.submit(() -> System.out.println("任务2开始执行"));
        executorService.submit(() -> System.out.println("任务3开始执行"));
        executorService.shutdown();
    }
}

方法七:invokeAll 方法结合 ExecutorService

invokeAll 方法可以并发地执行一组任务,并按顺序返回结果。

import java.util.concurrent.*;

public class InvokeAllOrder {
    private static final ExecutorService executorService = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        Callable task1 = () -> {
            System.out.println("任务1开始执行");
            return null;
        };

        Callable task2 = () -> {
            System.out.println("任务2开始执行");
            return null;
        };

        Callable task3 = () -> {
            System.out.println("任务3开始执行");
            return null;
        };

        try {
            executorService.invokeAll(Arrays.asList(task1, task2, task3));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        executorService.shutdown();
    }
}

具体使用哪种方法取决于应用场景和需求。如果需要简单的线程顺序执行,可以使用 join() 或线程池;如果需要更复杂的同步控制,可以使用 wait/notify、CountDownLatch、Phaser 等工具。

9.什么是乐观锁和悲观锁?

  • CAS 是基于乐观锁的思想:(Compare And Swap(比较再交换))最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

10.线程池的常见的核心参数有哪些?

  1. 核心线程数(corePoolSize):线程池中保持的最小线程数。
  2. 最大线程数(maximumPoolSize):线程池中允许的最大线程数。
  3. 线程存活时间(keepAliveTime):空闲线程在终止前等待新任务的时间。
  4. 时间单位(unit):线程存活时间的时间单位。
  5. 任务队列(workQueue):存储等待执行任务的队列。
  6. 线程工厂(threadFactory):用于创建线程的工厂类。
  7. 拒绝策略(rejectedExecutionHandler):处理无法执行新任务的策略。

11.怎么确定线程池的核心参数?

1.根据任务类型确定

  • CPU 密集型任务:如果任务以计算为主,CPU 利用率高,核心线程数可设置为 CPU 核心数 + 1。例如,如果 CPU 核心数为 4,核心线程数可设为 5。
  • IO 密集型任务:如果任务包含较多的 I/O 操作(如文件读写、网络通信),核心线程数可设置为 CPU 核心数 × 2 或更高。例如,CPU 核心数为 4,核心线程数可设为 8。
  • 混合型任务:如果任务同时包含计算和 I/O 操作,可综合考虑 CPU 和 I/O 的情况,适当调整核心线程数。例如,设置为 CPU 核心数 × 1.5。

2.根据硬件资源确定

  • 内存:如果内存资源有限,应减少核心线程数,以避免过多线程导致内存不足。
  • CPU 核心数:参考 CPU 核心数,配置核心线程数。例如,CPU 核心数为 4,核心线程数可以设置为 4 或 8(根据不同任务类型)。

3.根据任务并发量确定

  • 高并发场景:如果任务的并发量很高,核心线程数应足够大以应对并发请求。可以通过性能测试确定最佳的核心线程数。
  • 低并发场景:如果任务的并发量较低,核心线程数可以设置得较小,以节省系统资源。

4.根据业务需求确定

  • 响应时间:如果对任务的响应时间要求较高,应增加核心线程数,以减少任务的等待时间。
  • 吞吐量:如果更关注任务的吞吐量,核心线程数可以适当增加,以提高系统处理能力。

5.性能测试

  • 负载测试:通过负载测试工具(如 JMeter、LoadRunner),模拟不同线程数下的系统性能,观察吞吐量、响应时间和资源利用率等指标,确定最佳核心线程数。
  • 压力测试:施加压力测试,观察系统在不同线程数下的表现,确定系统能够承受的最大线程数。

6.参考公式

  • 对于 CPU 密集型任务,核心线程数可参考公式 corePoolSize = CPU 核心数 + 1。
  • 对于 IO 密集型任务,核心线程数可参考公式 corePoolSize = CPU 核心数 × (1 + W),其中 W 是等待时间与计算时间的比例。

参考工具

  • JConsole:用于监控线程池的状态和性能。
  • JProfiler:用于分析线程池的性能和资源使用情况。
  • VisualVM:用于监控和分析线程池的性能。

通过以上方法和工具,可以合理确定线程池的核心线程数,提高系统的性能和稳定性。

通过了解和掌握线程的生命周期,开发人员可以更好地管理和控制多线程程序的行为,避免出现线程安全问题和性能问题。

12.线程池的应用场景有哪些?

线程池在编程中有着广泛的应用,以下是主要应用场景:

1.高并发服务器处理

  • Web 服务器:如 Tomcat 等应用服务器使用线程池来处理 HTTP 请求。
  • 应用服务器:如电商平台服务器,处理用户的下单、支付等请求。

2.定时任务调度

  • 定期清理日志:每天凌晨 03:00 定时清理日志。
  • 定时备份数据:每周日晚上进行数据库备份。

3.Web 应用的请求处理

  • HTTP 请求:现代 Web 框架(如 Spring Boot)通过线程池管理 HTTP 请求。每个请求分配一个线程,可并行处理多个请求。

4.数据处理与计算

  • 大数据处理:将数据集分成多个任务,用线程池并行处理。
  • 科学计算:利用多线程并行计算复杂的数学问题,提高计算效率。

5.分布式系统的服务调用

  • 微服务架构:服务之间调用频繁,用线程池管理异步调用,提高系统吞吐量。

6.任务队列处理

  • 消息队列消费者:多个线程从队列中取出消息并行处理。
  • 通知任务:发送电子邮件或短信时,用线程池管理任务。

7.缓存管理

  • 缓存更新与清理:定期检查缓存,更新数据或清理过期缓存项。

线程池的应用场景丰富多样,基本涵盖各种需要批量、重复性、高并发任务处理的场景,是开发应用系统不可或缺的工具。

13.常见线程池的种类有哪些?

Java 中线程池主要有以下几种类型:

1.固定大小线程池(Fixed Thread Pool)

  • 特点:线程池的大小固定,一旦创建线程后,会保持线程数量直到线程池被关闭。
  • 适用场景:适用于任务量稳定且对响应时间有要求的场景。
  • 创建方式:使用 Executors.newFixedThreadPool(int nThreads) 方法创建。
  • 示例
ExecutorService executor = Executors.newFixedThreadPool(10); 
  • 创建一个大小为 10 的固定线程池。

2.可缓存线程池(Cached Thread Pool)

  • 特点:线程池的大小不固定,可以根据需要动态调整。空闲线程会等待一段指定的时间(默认 60 秒),如果在这段时间内没有任务需要执行,线程会被终止。
  • 适用场景:适用于执行许多短期异步任务的小程序。
  • 创建方式:使用 Executors.newCachedThreadPool() 方法创建。
  • 示例
ExecutorService executor = Executors.newCachedThreadPool(); 
  • 创建一个可缓存线程池。

3.单线程线程池(Single Thread Executor)

  • 特点:线程池中只有一个线程,确保所有任务按照顺序执行。
  • 适用场景:适用于需要任务串行化执行,确保任务顺序的场景。
  • 创建方式:使用 Executors.newSingleThreadExecutor() 方法创建。
  • 示例
ExecutorService executor = Executors.newSingleThreadExecutor(); 
  • 创建一个单线程线程池。

4.定时任务线程池(Scheduled Thread Pool)

  • 特点:支持定时和周期性任务执行。
  • 适用场景:适用于需要定时执行任务的场景,如定期清理日志、定时备份数据等。
  • 创建方式:使用 Executors.newScheduledThreadPool(int corePoolSize) 方法创建。
  • 示例
ScheduledExecutorService executor = Executors.newScheduledThreadPool(4); 
  • 创建一个大小为 4 的定时任务线程池。

5.单线程定时任务线程池(Single Thread Scheduled Executor)

  • 特点:线程池中只有一个线程,支持定时和周期性任务执行。
  • 适用场景:适用于需要定时执行任务且任务顺序重要的场景。
  • 创建方式:使用 Executors.newSingleThreadScheduledExecutor() 方法创建。
  • 示例
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); 

6.自定义线程池(Custom Thread Pool)

  • 特点:可以根据业务需求自定义线程池的参数,如核心线程数、最大线程数、线程存活时间等。
  • 适用场景:适用于对线程池有特殊需求的场景,如高并发数据处理。
  • 创建方式:使用 ThreadPoolExecutor 类的构造函数创建。
  • 示例
  • java复制
ThreadPoolExecutor executor = new ThreadPoolExecutor

14.什么是volatile?它的作用是什么?

  • volatile 的作用:保证变量的可见性和禁止指令重排序。

15.如何处理多线程中的线程安全问题?

使用同步机制、原子变量、线程安全类、不可变对象等;

16.如何调试多线程程序?

使用日志记录、线程转储、性能分析工具(如 JProfiler、JVisualVM)等。

17.如何优化多线程程序的性能?

减少锁的粒度、使用无锁并发、合理配置线程池、避免线程阻塞等。

18.什么是线程的守护线程?它的作用是什么?

JVM 应用程序退出时,会自动销毁守护线程。主要用于为其他线程提供服务,如垃圾回收线程。

19.如何在多线程中实现线程的通信?

使用 `wait`、`notify`、`notifyAll`,或者 `Condition` 等。

我是阳仔,喜欢的朋友,欢迎点赞,转发,评论!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

deepseek 3FS编译

3FS在ubuntu22.04下的编译&#xff08;记录下编译过程&#xff0c;方便后续使用&#xff09; 环境信息 OS ubuntu 22.04内核版本 6.8.0-52-genericlibfuse 3.16.1rust 1.75.0FoundationDB 7.1.66meson 1.0.0ninja 1.10.1 libfuse编译 以下建议均在root下执行 pip3 install…

docker-compose安装redis-主从+哨兵(3台虚拟机一主两从)

一、部署架构 ‌主从结构‌ ‌主节点‌&#xff1a;部署于192.168.56.100‌从节点‌&#xff1a;部署于192.168.56.101和192.168.56.103 ‌哨兵结构‌ 每台服务器部署一个哨兵实例&#xff0c;形成三哨兵集群。 ipredis节点名称sentinel节点名称192.168.56.100redis-masterr…

vue+element 实现蛇形时间轴 拐弯时间轴

公司业务需要做一个如图效果 分享给有需要的人 有更好的意见欢迎交流 核心代码如下 <div style"display: flex; position: relative"><div style"width: 89%; margin: auto; padding: 10px 0"><div v-for"(item, index) in experien…

DeepSeek-R1-671B大模型满血版私有化部署高可用教程-SparkAi系统集成图文教程

DeepSeek官网服务器繁忙的主要原因是由于用户数量激增导致的服务器资源紧张。‌为了解决这一问题&#xff0c;DeepSeek团队已经暂停了API服务充值&#xff0c;以避免对用户造成业务影响。目前&#xff0c;存量充值金额仍可继续调用&#xff0c;但充值功能暂时不可用‌。 DeepSe…

动态扩缩容引发的JVM堆内存震荡:从原理到实践的GC调优指南

目录 一、典型案例&#xff1a;系统发布后的GC雪崩事件 &#xff08;一&#xff09;故障现象 1. 刚刚启动时 GC 次数较多 2. 堆内存锯齿状波动 3. GC日志特征&#xff1a;Allocation Failure &#xff08;二&#xff09;问题定位 二、原理深度解析&#xff1a;JVM内存弹…

OpenCV计算摄影学(14)实现对比度保留去色(Contrast Preserving Decolorization)的函数decolor()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将彩色图像转换为灰度图像。它是数字印刷、风格化的黑白照片渲染&#xff0c;以及许多单通道图像处理应用中的基本工具。 cv::decolor 是 OpenCV…

STM32 ADC模数转换

目录 ADC简介逐次逼近型ADCSTM32的ADC输入通道规则组的转换模式单次转换、非扫描模式连续转换、非扫描模式单次转换、扫描模式连续转换、扫描模式间断模式 数据对齐转换时间校准代码软件触发单次转换非扫描模式 ADC简介 ADC&#xff08;Analog-Digital Converter&#xff09;模…

Facebook 的历史与发展:从校园网站到全球社交平台

引言 Facebook&#xff0c;这个全球最大的社交网络平台之一&#xff0c;其发展历程充满了创新和变革。从最初的校园网站到如今的全球社交平台&#xff0c;Facebook 不仅改变了人们的沟通方式&#xff0c;也重塑了信息传播和社交互动的模式。 起源&#xff1a;校园内的点子 Fa…

自然语言转SQL之Vanna.ai:AI集成数据库

自然语言转SQL之Vanna.ai&#xff1a;AI集成数据库 一、Vanna.ai是什么二、落地步骤&#xff1a;实现三层需求2.1 官方示例看效果2.2 对接自己的数据库2.3 完全本地化之路 三、构建自己的产品3.1 提问转SQL3.2 执行SQL查询实例2 要实现的功能就是&#xff1a;用中文语言同数据库…

Android 平台架构系统启动流程详解

目录 一、平台架构模块 1.1 Linux 内核 1.2 硬件抽象层 (HAL) 1.3 Android 运行时 1.4 原生 C/C 库 1.5 Java API 框架 1.6 系统应用 二、系统启动流程 2.1 Bootloader阶段 2.2 内核启动 2.3 Init进程&#xff08;PID 1&#xff09; 2.4 Zygote与System Serv…

QT 作业 C++ day5

作业 代码 MyQThread.h class MyThread : public QThread {Q_OBJECT public:MyThread(QObject *parent nullptr); protected:void run() override; signals://向ui界面发送的 "复制进度" 的信号void copy_process_signal(int index); public slots:// "复…

24、如何在C++中创建和管理线程?【中高频】 -

创建线程对象&#xff1a; //无参构造&#xff0c;该线程对象没有关联任何线程函数&#xff0c;也就是它没有启动任何线程:thread t1;//... t1 thread(func, 10);//移动构造&#xff08;调用移动赋值函数&#xff09;t1.join();//含参构造thread t1(func1, 1, 10);//thread 提…

【Altium】22.11版本后如何导出Gerber镜像层

1、 文档目标 解决 22.11 版本后如何导出 Gerber 镜像层的问题 2、 问题场景 Gerber 导出旧版本&#xff0c;在 AD 22.11 之前的 Gerber 导出中是存在镜像层的选择。 图 1 软件更新至 AD22.11 及之后版本&#xff0c;在 Gerber 导出设置中无法选择层镜像进行导出。 图 2 3、…

配置 Thunderbird 以使用 QQ 邮箱

配置 Thunderbird 以使用 QQ 邮箱 本片文章的操作系统为 windws 10 &#xff0c;thunder bird 客户端版本为 128.7.1esr(64位)。注意到其他文章的图片中 thunder bird 的 ui 界面和我这个不一样&#xff0c;导致看起来不太方便&#xff0c;所以这里写一篇博客。不同版本的 thu…

wxWidgets GUI 跨平台 入门学习笔记

准备 参考 https://wiki.wxwidgets.org/Microsoft_Visual_C_NuGethttps://wiki.wxwidgets.org/Tools#Rapid_Application_Development_.2F_GUI_Buildershttps://docs.wxwidgets.org/3.2/https://docs.wxwidgets.org/latest/overview_helloworld.htmlhttps://wizardforcel.gitb…

DeepSeek系列模型技术报告的阅读笔记

DeepSeek系列模型技术报告的阅读笔记 之前仔细阅读了DeepSeek系列模型的主要技术方面内容与发展脉络&#xff0c;以下是DeepSeek系列模型技术报告的笔记&#xff0c;有错误的地方欢迎指正&#xff01; 文章目录 DeepSeek系列模型技术报告的阅读笔记GQADeepseek MoEAbstractIn…

海思Hi3516DV300交叉编译opencv

OpenCV是一个开源的跨平台计算机视觉库&#xff0c;支持C、Python等多种语言&#xff0c;适用于图像处理、目标检测、机器学习等任务。其核心由C编写&#xff0c;高效轻量&#xff0c;提供实时视觉处理功能&#xff0c;广泛应用于工业自动化、医疗影像等领域。 1 环境准备 1…

React + React-intl @3.xx + TypeScript

声明&#xff1a;此篇文章使用的版本是 "react-intl": "^3.12.0"。 因为react-intl3.xx版本相较于react-intl2.xx版本差别较大&#xff0c;有些API是break change, 所以这篇文章的实现方式&#xff0c;不适用于react-intl2.xx版本。 一: 安装react-intl np…

(二 十 二)趣学设计模式 之 备忘录模式!

目录 一、 啥是备忘录模式&#xff1f;二、 为什么要用备忘录模式&#xff1f;三、 备忘录模式的实现方式四、 备忘录模式的优缺点五、 备忘录模式的应用场景六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;…

20250307确认荣品PRO-RK3566开发板在Android13下的以太网络共享功能

20250307确认荣品PRO-RK3566开发板在Android13下的以太网络共享功能 2025/3/7 13:56 缘起&#xff1a;我司地面站需要实现“太网络共享功能”功能。电脑PC要像连接WIFI热点一样连接在Android设备/平板电脑上来实现上网功能/数据传输。 Android设备/平板电脑通过4G/WIFI来上网。…