当前位置: 首页 > news >正文

Java线程详解

一、线程的基本概念

1. 什么是线程?

  • 线程是程序执行的一个单元,它是进程中的一个实体,是被系统独立调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间和文件句柄,但每个线程有自己的程序计数器、栈和局部变量等。

2. 线程的优势

  • 提高资源利用率:在多处理器或多核系统中,多个线程可以同时运行在不同的处理器核心上,充分利用系统资源,提高程序的执行效率。
  • 提高响应性:对于用户界面程序,将耗时的操作放在后台线程执行,主线程继续响应用户操作,提高用户体验。

二、线程的创建和启动

1. 继承 Thread

class MyThread extends Thread {@Overridepublic void run() {// 线程执行的代码System.out.println("Thread is running");}
}public class ThreadExample {public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
}
  • 解释:
    • 定义一个类 MyThread 继承 Thread 类,并重写 run() 方法,将线程要执行的代码放在 run() 方法中。
    • main() 方法中创建 MyThread 实例,并调用 start() 方法启动线程,调用 start() 会使线程进入就绪状态,等待系统调度执行,而不是直接调用 run() 方法,直接调用 run() 方法只是普通的方法调用,不会启动新线程。

2. 实现 Runnable 接口

class MyRunnable implements Runnable {@Overridepublic void run() {// 线程执行的代码System.out.println("Thread is running");}
}public class RunnableExample {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}
  • 解释:
    • 定义一个类 MyRunnable 实现 Runnable 接口,并重写 run() 方法。
    • 创建 MyRunnable 的实例,将其作为参数传递给 Thread 类的构造函数,然后调用 start() 方法启动线程。

3. 使用 CallableFuture(带返回值的线程)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 线程执行的代码return 42;}
}public class CallableExample {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable = new MyCallable();FutureTask<Integer> futureTask = new FutureTask<>(myCallable);Thread thread = new Thread(futureTask);thread.start();// 获取线程的返回值Integer result = futureTask.get();System.out.println("Thread result: " + result);}
}
  • 解释:
    • 定义一个类 MyCallable 实现 Callable 接口,重写 call() 方法,该方法可以有返回值,并且可以抛出异常。
    • 创建 FutureTask 对象,将 MyCallable 实例作为参数传递给 FutureTask 的构造函数。
    • 创建 Thread 对象,将 FutureTask 作为参数传递给 Thread 的构造函数并启动线程。
    • 使用 futureTask.get() 方法获取线程执行的结果,该方法会阻塞直到线程执行完成并返回结果,如果线程未完成,调用线程会等待。

三、线程的生命周期

Java 线程的生命周期主要包括以下几个状态:

  • 新建(New):当创建了 Thread 类的实例,但还未调用 start() 方法时,线程处于新建状态。
  • 就绪(Runnable):调用 start() 方法后,线程进入就绪状态,等待系统调度。
  • 运行(Running):当线程被系统选中并开始执行 run()call() 方法时,线程处于运行状态。
  • 阻塞(Blocked):线程可能因为等待锁、等待 I/O 操作完成、调用 wait() 方法等而进入阻塞状态,暂时停止执行。
  • 等待(Waiting):线程调用 wait()join()LockSupport.park() 等方法会进入等待状态,直到其他线程通知或中断。
  • 超时等待(Timed Waiting):线程调用 sleep()wait(long)join(long)LockSupport.parkNanos() 等方法,在等待一段时间后自动唤醒。
  • 终止(Terminated):线程执行完 run()call() 方法,或者出现异常而终止。

四、线程的同步

1. synchronized 关键字

  • 同步方法:
class Counter {private int count = 0;public synchronized void increment() {count++;}public int getCount() {return count;}
}
  • 解释:

    • synchronized 修饰方法时,同一时间只有一个线程可以执行该方法,保证了方法的同步。
  • 同步代码块:

class Counter {private int count = 0;private final Object lock = new Object();public void increment() {synchronized (lock) {count++;}}public int getCount() {return count;}
}
  • 解释:
    • 使用 synchronized 同步代码块,通过一个对象锁(这里是 lock 对象)来保证代码块内的代码在同一时间只有一个线程可以执行。

2. ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class Counter {private int count = 0;private final Lock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public int getCount() {return count;}
}
  • 解释:
    • 使用 ReentrantLock 类,在进入代码块前调用 lock() 方法加锁,在代码块执行完后在 finally 块中调用 unlock() 方法释放锁,确保锁的释放,避免死锁。

五、线程间通信

1. wait()notify()notifyAll() 方法

class MessageQueue {private final Object lock = new Object();private String message;public void put(String message) {synchronized (lock) {while (this.message!= null) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}this.message = message;lock.notifyAll();}}public String take() {synchronized (lock) {while (message == null) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}String result = message;message = null;lock.notifyAll();return result;}}
}
  • 解释:
    • wait() 方法使线程进入等待状态,直到被 notify()notifyAll() 方法唤醒。
    • notify() 唤醒一个等待的线程,notifyAll() 唤醒所有等待的线程。

2. BlockingQueue 接口及其实现类

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;public class BlockingQueueExample {public static void main(String[] args) throws InterruptedException {BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);queue.put("Message 1");String message = queue.take();System.out.println(message);}
}
  • 解释:
    • BlockingQueue 是一个阻塞队列,提供了 put()take() 等方法,当队列满时 put() 会阻塞,当队列空时 take() 会阻塞,简化了线程间的数据传递。

六、线程池

1. 使用 ExecutorServiceThreadPoolExecutor

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {executorService.execute(() -> {System.out.println("Thread is running");});}executorService.shutdown();}
}
  • 解释:
    • Executors.newFixedThreadPool(5) 创建一个固定大小为 5 的线程池。
    • executorService.execute() 方法将任务提交给线程池执行。
    • executorService.shutdown() 方法关闭线程池。

2. 自定义 ThreadPoolExecutor

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;public class CustomThreadPoolExample {public static void main(String[] args) {int corePoolSize = 2;int maximumPoolSize = 5;long keepAliveTime = 10;TimeUnit unit = TimeUnit.SECONDS;ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);ThreadFactory threadFactory = Executors.defaultThreadFactory();RejectedExecutionHandler handler = new AbortPolicy();ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);for (int i = 0; i < 10; i++) {executor.execute(() -> {System.out.println("Thread is running");});}executor.shutdown();}
}
  • 解释:
    • ThreadPoolExecutor 提供了更灵活的线程池配置,包括核心线程数、最大线程数、线程存活时间、队列容量等。

七、线程的调度和优先级

1. 线程优先级

class MyThread extends Thread {public MyThread(String name) {super(name);}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " is running");}
}public class ThreadPriorityExample {public static void main(String[] args) {MyThread thread1 = new MyThread("Thread 1");MyThread thread2 = new MyThread("Thread 2");thread1.setPriority(Thread.MAX_PRIORITY);thread2.setPriority(Thread.MIN_PRIORITY);thread1.start();thread2.start();}
}
  • 解释:
    • setPriority() 方法可以设置线程的优先级,范围从 1(最低)到 10(最高),但优先级只是一个建议,实际的调度由操作系统决定。

2. 线程调度

  • Java 线程的调度由操作系统负责,操作系统根据线程的状态和优先级等因素来调度线程的执行,开发人员可以通过 yield() 方法让当前线程让出 CPU 资源,让其他线程有机会执行,但不保证一定有效。

八、线程的中断

1. interrupt() 方法

class MyThread extends Thread {@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()) {// 线程执行的代码}System.out.println("Thread interrupted");}
}public class ThreadInterruptExample {public static void main(String[] args) throws InterruptedException {MyThread thread = new MyThread();thread.start();Thread.sleep(1000);thread.interrupt();}
}
  • 解释:
    • interrupt() 方法用于中断线程,调用该方法会设置线程的中断标志。
    • 线程可以通过 isInterrupted() 方法检查中断标志,或者在阻塞操作中抛出 InterruptedException 来响应中断。

九、并发工具类

1. CountDownLatch

import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(3);new Thread(() -> {latch.countDown();}).start();new Thread(() -> {latch.countDown();}).start();new Thread(() -> {latch.countDown();}).start();latch.await();System.out.println("All threads have finished");}
}
  • 解释:
    • CountDownLatch 可以让一个或多个线程等待其他线程完成操作,countDown() 方法将计数器减 1,await() 方法使当前线程等待计数器为 0。

2. CyclicBarrier

import java.util.concurrent.CyclicBarrier;public class CyclicBarrierExample {public static void main(String[] args) {CyclicBarrier barrier = new CyclicBarrier(3, () -> {System.out.println("All threads have reached the barrier");});new Thread(() -> {try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();new Thread(() -> {try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();new Thread(() -> {try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();}
}
  • 解释:
    • CyclicBarrier 让一组线程互相等待,当所有线程都到达屏障时,执行指定的操作。

3. Semaphore

import java.util.concurrent.Semaphore;public class SemaphoreExample {public static void main(String[] args) {Semaphore semaphore = new Semaphore(2);new Thread(() -> {try {semaphore.acquire();System.out.println("Thread 1 acquired the semaphore");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(() -> {try {semaphore.acquire();System.out.println("Thread 2 acquired the semaphore");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
  • 解释:
    • Semaphore 是一个计数信号量,控制同时访问某个资源的线程数量,acquire() 方法获取许可证,release() 方法释放许可证。

通过上述的讲解,你可以对 Java 线程的创建、生命周期、同步、通信、池化、调度、中断以及并发工具类有一个较为全面的了解。在实际开发中,根据不同的场景选择合适的线程和并发工具,可以提高程序的性能和可维护性。同时,需要注意多线程编程中的并发问题,如死锁、资源竞争等,确保程序的正确性和稳定性。

Java 线程在实际应用中的典型例子

一、文件下载器

假设你正在开发一个文件下载器应用程序,为了提高下载速度,可以使用多线程同时下载文件的不同部分。

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FileDownloader {private static final int THREAD_COUNT = 5;private static final int BUFFER_SIZE = 4096;public static void main(String[] args) throws Exception {String fileUrl = "http://example.com/largefile.zip";String savePath = "largefile.zip";URL url = new URL(fileUrl);URLConnection connection = url.openConnection();int fileSize = connection.getContentLength();int partSize = fileSize / THREAD_COUNT;ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);try (OutputStream outputStream = new FileOutputStream(savePath)) {for (int i = 0; i < THREAD_COUNT; i++) {int startByte = i * partSize;int endByte = (i == THREAD_COUNT - 1)? fileSize - 1 : (i + 1) * partSize - 1;executorService.execute(() -> {try {downloadPart(url, outputStream, startByte, endByte);} catch (Exception e) {e.printStackTrace();}});}} finally {executorService.shutdown();}}private static void downloadPart(URL url, OutputStream outputStream, int startByte, int endByte) throws Exception {URLConnection connection = url.openConnection();connection.setRequestProperty("Range", "bytes=" + startByte + "-" + endByte);try (InputStream inputStream = connection.getInputStream()) {byte[] buffer = new byte[BUFFER_SIZE];int bytesRead;long totalBytesRead = 0;while ((bytesRead = inputStream.read(buffer))!= -1 && totalBytesRead < (endByte - startByte + 1)) {outputStream.write(buffer, 0, bytesRead);totalBytesRead += bytesRead;}}}
}

解释

  • 该程序使用 ExecutorService 创建一个固定大小的线程池,将文件分成多个部分,每个线程负责下载文件的一部分。
  • downloadPart 方法通过设置 Range 请求头来指定下载文件的范围,确保每个线程下载文件的不同部分。
  • 每个线程从输入流中读取数据并写入输出流,最终将文件的不同部分拼接成完整的文件。

二、服务器并发处理

在开发一个简单的服务器程序时,可以使用多线程处理客户端的并发请求。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SimpleServer {private static final int PORT = 8080;private static final int THREAD_POOL_SIZE = 10;public static void main(String[] args) throws IOException {ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);try (ServerSocket serverSocket = new ServerSocket(PORT)) {System.out.println("Server is listening on port " + PORT);while (true) {Socket socket = serverSocket.accept();executorService.execute(() -> handleClient(socket));}} finally {executorService.shutdown();}}private static void handleClient(Socket socket) {try (BuffufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {String clientRequest = reader.readLine();System.out.println("Received from client: " + clientRequest);String response = "Hello, client!";writer.println(response);} catch (IOException e) {e.printStackTrace();} finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

解释

  • ServerSocket 监听指定端口,当有客户端连接时,将连接交给线程池中的线程处理。
  • handleClient 方法处理客户端请求,读取客户端发送的数据并发送响应,确保每个客户端请求都能得到及时处理。

三、生产者-消费者模式

在生产者-消费者模式中,生产者生成数据,消费者消费数据,中间通过一个队列存储数据。

import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class Producer implements Runnable {private final BlockingQueue<String> queue;public Producer(BlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {try {String data = "Data " + i;queue.put(data);System.out.println("Produced: " + data);Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
}class Consumer implements Runnable {private final BlockingQueue<String> queue;public Consumer(BlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true) {try {String data = queue.take();System.out.println("Consumed: " + data);Thread.sleep(200);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}
}public class ProducerConsumerExample {public static void main(String[] args) {BlockingQueue<String> queue = new LinkedBlockingQueue<>();Thread producerThread = new Thread(new Producer(queue));Thread consumerThread = new Thread(new Consumer(queue));producerThread.start();consumerThread.start();}
}

解释

  • Producer 类负责生产数据并将其放入 BlockingQueue 中,Consumer 类从队列中取出数据并消费。
  • BlockingQueue 保证了线程安全,当队列满时生产者阻塞,当队列空时消费者阻塞,避免了生产者和消费者之间的同步问题。

四、并行计算

对于一些计算密集型任务,可以使用多线程进行并行计算,例如计算斐波那契数列。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;class FibonacciTask implements Callable<Integer> {private final int n;public FibonacciTask(int n) {this.n = n;}@Overridepublic Integer call() throws Exception {if (n <= 1) {return n;}return fibonacci(n - 1) + fibonacci(n - 2);}private int fibonacci(int n) {if (n <= 1) {return n;}return fibonacci(n - 1) + fibonacci(n - 2);}
}public class ParallelFibonacci {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(4);Future<Integer> future1 = executorService.submit(new FibonacciTask(30));Future<Integer> future2 = executorService.submit(new FibonacciTask(35));System.out.println("Fibonacci(30): " + future1.get());System.out.println("Fibonacci(35): " + future2.get());executorService.shutdown();}
}

解释

  • FibonacciTask 类实现 Callable 接口,计算斐波那契数列的第 n 项。
  • ExecutorService 用于提交多个 FibonacciTask 到线程池进行并行计算,通过 Future 类的 get() 方法获取计算结果。

五、定时任务

使用 Java 线程实现定时任务,可以使用 ScheduledExecutorService

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledTask {public static void main(String[] args) {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);executorService.scheduleAtFixedRate(() -> {System.out.println("Task executed at " + System.currentTimeMillis());}, 0, 1, TimeUnit.SECONDS);}
}

解释

  • ScheduledExecutorService 用于执行定时任务,scheduleAtFixedRate 方法按照固定的时间间隔执行任务。

通过这些实际应用示例,可以看到 Java 线程在不同场景下的使用方式,包括并发处理、资源下载、生产者-消费者模式、并行计算和定时任务。在实际开发中,合理使用线程可以显著提高程序的性能和响应速度,但需要注意线程安全和资源竞争等问题。根据具体的业务需求和性能要求,可以灵活运用不同的线程创建和管理方式。

在上述示例中,不同的场景使用了不同的线程创建和管理方式,包括 ExecutorServiceThreadPoolExecutorRunnableCallableFuture 等,你可以根据自己的需求进行调整和扩展。

以下是 Java 线程在面试中经常会被问到的一些问题:

一、基础概念类问题

1. 什么是线程?线程和进程的区别是什么?
  • 回答
    • 线程是程序执行的一个单元,是进程中的一个实体,是被系统独立调度和分派的基本单位。一个进程可以包含多个线程,它们共享进程的资源,如内存空间、文件句柄等,但每个线程拥有自己的程序计数器、栈和局部变量。
    • 进程是程序的一次执行过程,是系统资源分配的基本单位,包括代码、数据和系统资源,进程之间相互独立,拥有独立的内存空间,进程间的切换开销较大。而线程是进程中的执行单元,线程切换的开销相对较小,因为它们共享进程的资源。
2. 如何创建一个线程?有哪些方式?
  • 回答
    • 继承 Thread
class MyThread extends Thread {@Overridepublic void run() {// 线程要执行的代码System.out.println("Thread is running");}
}public class ThreadExample {public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
}
- **实现 `Runnable` 接口**:
class MyRunnable implements Runnable {@Overridepublic void run() {// 线程要执行的代码System.out.println("Thread is running");}
}public class RunnableExample {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}
- **使用 `Callable` 和 `Future`(带返回值的线程)**:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 线程要执行的代码return 42;}
}public class CallableExample {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable = new MyCallable();FutureTask<Integer> futureTask = new FutureTask<>(myCallable);Thread thread = new Thread(futureTask);thread.start();// 获取线程的返回值Integer result = futureTask.get();System.out.println("Thread result: " + result);}
}

二、线程状态类问题

1. 请描述 Java 线程的生命周期。
  • 回答
    • 新建(New):创建 Thread 类的实例,但尚未调用 start() 方法时,线程处于新建状态。
    • 就绪(Runnable):调用 start() 方法后,线程进入就绪状态,等待系统分配 CPU 资源。
    • 运行(Running):线程获得 CPU 资源,开始执行 run()call() 方法。
    • 阻塞(Blocked):线程因等待锁、I/O 操作、调用 wait() 等进入阻塞状态,暂时停止执行。
    • 等待(Waiting):线程调用 wait()join()LockSupport.park() 等进入等待状态,等待其他线程唤醒。
    • 超时等待(Timed Waiting):线程调用 sleep()wait(long)join(long)LockSupport.parkNanos() 等进入等待一段时间后自动唤醒的状态。
    • 终止(Terminated):线程完成任务或出现异常而终止。
2. 如何让一个线程暂停和恢复执行?
  • 回答
    • 暂停:可以使用 Thread.sleep(long) 让线程暂停一段时间;使用 wait() 方法使线程进入等待状态,需要其他线程调用 notify()notifyAll() 唤醒;使用 join() 让当前线程等待另一个线程执行完毕。
    • 恢复:使用 notify()notifyAll() 唤醒等待的线程;对于 join(),当被等待的线程执行完毕,当前线程会自动恢复。

三、线程同步类问题

1. 解释 synchronized 关键字的作用。
  • 回答
    • synchronized 可以用于修饰方法或代码块,保证同一时间只有一个线程可以访问被修饰的方法或代码块,实现线程同步,防止多个线程同时访问共享资源导致的数据不一致问题。例如:
class Counter {private int count = 0;public synchronized void increment() {count++;}public int getCount() {return count;}
}

class Counter {private int count = 0;private final Object lock = new Object();public void increment() {synchronized (lock) {count++;}}public int getCount() {return count;}
}
2. 什么是死锁?如何避免死锁?
  • 回答
    • 死锁:多个线程互相等待对方释放锁,导致程序无法继续执行的情况。例如,线程 A 持有锁 X 并等待锁 Y,线程 B 持有锁 Y 并等待锁 X,就会发生死锁。
    • 避免死锁的方法
      • 按顺序获取锁,避免循环等待。
      • 尽量减少锁的使用,缩小同步代码块的范围。
      • 使用 tryLock() 方法尝试获取锁,避免死锁。
3. 如何使用 ReentrantLock 进行线程同步?
  • 回答
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class Counter {private int count = 0;private final Lock lock = new ReentrantLock();public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public int getCount() {return count;}
}
  • ReentrantLock 提供了更灵活的锁机制,通过 lock() 加锁,在 finally 块中使用 unlock() 释放锁,避免死锁。

四、线程间通信类问题

1. 解释 wait()notify()notifyAll() 的使用方法。
  • 回答
    • wait() 使线程进入等待状态,释放锁,直到其他线程调用 notify()notifyAll() 唤醒。
    • notify() 唤醒一个等待的线程,notifyAll() 唤醒所有等待的线程。例如:
class MessageQueue {private final Object lock = new Object();private String message;public void put(String message) {synchronized (lock) {while (this.message!= null) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}this.message = message;lock.notifyAll();}}public String take() {synchronized (lock) {while (message == null) {try {lock.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}String result = message;message = null;lock.notifyAll();return result;}}
}
2. 如何使用 BlockingQueue 实现线程间通信?
  • 回答
    • BlockingQueue 是一个阻塞队列,提供 put()take() 方法,当队列满时 put() 阻塞,当队列空时 take() 阻塞,方便线程间的数据传递。例如:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;public class BlockingQueueExample {public static void main(String[] args) throws InterruptedException {BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);queue.put("Message 1");String message = queue.take();System.out.println(message);}
}

五、线程池类问题

1. 什么是线程池?为什么要使用线程池?
  • 回答
    • 线程池是一种线程管理机制,它管理着多个线程,通过复用线程,避免频繁创建和销毁线程的开销。使用线程池可以提高性能,方便管理线程的生命周期,控制并发线程的数量。
2. 如何创建和使用线程池?
  • 回答
    • 可以使用 ExecutorServiceThreadPoolExecutor 创建线程池,例如:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < 10; i++) {executorService.execute(() -> {System.out.println("Thread is running");});}executorService.shutdown();}
}

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor.AbortPolicy;public class CustomThreadPoolExample {public static void main(String[] args) {int corePoolSize = 2;int maximumPoolSize = 5;long keepAliveTime = 10;TimeUnit unit = TimeUnit.SECONDS;ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);ThreadFactory threadFactory = Executors.defaultThreadFactory();RejectedExecutionHandler handler = new AbortPolicy();ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);for (int i = 0; i < 10; i++) {executor.execute(() -> {System.out.println("Thread is running");});}executor.shutdown();}
}

六、并发工具类问题

1. 解释 CountDownLatch 的作用和使用方法。
  • 回答
    • CountDownLatch 可以让一个或多个线程等待其他线程完成操作。例如:
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(3);new Thread(() -> {latch.countDown();}).start();new Thread(() -> {latch.countDown();}).start();new Thread(() -> {latch.countDown();}).start();latch.await();System.out.println("All threads have finished");}
}
  • 多个线程调用 countDown() 减少计数,当计数为 0 时,调用 await() 的线程继续执行。
2. 解释 CyclicBarrier 的作用和使用方法。
  • 回答
    • CyclicBarrier 让一组线程互相等待,当所有线程都到达屏障时,执行指定操作。例如:
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierExample {public static void main(String[] args) {CyclicBarrier barrier = new CyclicBarrier(3, () -> {System.out.println("All threads have reached the barrier");});new Thread(() -> {try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();new Thread(() -> {try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();new Thread(() -> {try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();}
}
3. 解释 Semaphore 的作用和使用方法。
  • 回答
    • Semaphore 是一个计数信号量,控制同时访问某个资源的线程数量。例如:
import java.util.concurrent.Semaphore;public class SemaphoreExample {public static void main(String[] args) {Semaphore semaphore = new Semaphore(2);new Thread(() -> {try {semaphore.acquire();System.out.println("Thread 1 acquired the semaphore");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(() -> {try {semaphore.acquire();System.out.println("Thread 2 acquired the semaphore");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}

七、线程安全类问题

1. 如何保证线程安全?
  • 回答
    • 使用 synchronized 关键字或 ReentrantLock 进行同步。
    • 使用线程安全的集合类,如 ConcurrentHashMapCopyOnWriteArrayList 等。
    • 使用 Atomic 类,如 AtomicIntegerAtomicBoolean 等,它们利用底层的原子操作保证线程安全。
2. 什么是线程安全的单例模式?
  • 回答
    • 饿汉式单例模式
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
- **懒汉式单例模式(使用 `synchronized`)**:
public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
- **双重检查锁定单例模式(DCL)**:
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

这些问题涵盖了 Java 线程的基本概念、创建、同步、通信、池化、并发工具类和线程安全等方面,在面试中较为常见。掌握这些知识将有助于你更好地回答 Java 线程相关的问题,展现你对 Java 多线程编程的理解和应用能力。

http://www.lryc.cn/news/520811.html

相关文章:

  • java -jar启动项目报错:XXX.jar中没有主清单属性
  • 【Vue - Element 】实现表单输入框的远程搜索功能
  • Linux(Centos 7.6)命令详解:split
  • 八股学习 Redis
  • 如何通过高防服务隐藏服务器源IP
  • 【WEB】网络传输中的信息安全 - 加密、签名、数字证书与HTTPS
  • 借助Claude实现Playwright的自动化(MCP Server)
  • 【区间DP】【hard】力扣730. 统计不同回文子序列
  • 【Vim Masterclass 笔记11】S06L24 + L25:Vim 文本的插入、变更、替换与连接操作同步练习(含点评课)
  • 分布式组件底层逻辑是什么?
  • Spring Boot中的扫描注解如何使用
  • 初识JVM HotSopt 的发展历程
  • 基于springboot果蔬供应链信息管理平台
  • 掌握 React 关键:理解 super () 和 super (props) 的不同应用
  • Scala语言的软件开发工具
  • 斯坦福大学李飞飞教授团队ARCap: 利用增强现实反馈收集高质量的人类示教以用于机器人学习
  • 【Linux】从零开始:编写你的第一个Linux进度条小程序
  • web前端第八次作业---制作音乐榜单
  • 心脏扩散张量成像中的异常值检测:射击拒绝还是稳健拟合?|文献速递-视觉大模型医疗图像应用
  • Linux Kernel 之十 详解 PREEMPT_RT、Xenomai 的架构、源码、构建及使用
  • RabbitMQ-消息消费确认
  • E10.【C语言】练习:编写一个猜数字游戏
  • RK3568-rk809rtc休眠唤醒
  • 【Uniapp-Vue3】pages.json页面路由globalStyle的属性
  • NHANES数据挖掘|特征变量对死亡率预测的研究设计与分析
  • 【Sharding-JDBC学习】概述_shardingsphere-jdbc 和sharding-jdbc
  • 用户登录/登出功能,当登录页面在另一域名下
  • 自动化解决方案:修复devicedisplaystatusmanager.dll丢失
  • .Net8 Avalonia跨平台UI框架——<vlc:VideoView>控件播放海康监控、摄像机视频(Windows / Linux)
  • 网络协议(八):IP 协议