Bootstrap

Java线程详解

一、线程的基本概念

1. 什么是线程?

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

2. 线程的优势

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

二、线程的创建和启动

1. 继承 Thread

class MyThread extends Thread {
    @Override
    public 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 {
    @Override
    public 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> {
    @Override
    public 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);
    }

    @Override
    public 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 {
    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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 {
    @Override
    public 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 {
    @Override
    public 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> {
    @Override
    public 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 多线程编程的理解和应用能力。

;