一、线程的基本概念
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. 使用 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);
}
}
- 解释:
- 定义一个类
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. 使用 ExecutorService
和 ThreadPoolExecutor
:
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 线程在不同场景下的使用方式,包括并发处理、资源下载、生产者-消费者模式、并行计算和定时任务。在实际开发中,合理使用线程可以显著提高程序的性能和响应速度,但需要注意线程安全和资源竞争等问题。根据具体的业务需求和性能要求,可以灵活运用不同的线程创建和管理方式。
在上述示例中,不同的场景使用了不同的线程创建和管理方式,包括 ExecutorService
、ThreadPoolExecutor
、Runnable
、Callable
和 Future
等,你可以根据自己的需求进行调整和扩展。
以下是 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):线程完成任务或出现异常而终止。
- 新建(New):创建
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. 如何创建和使用线程池?
- 回答:
- 可以使用
ExecutorService
和ThreadPoolExecutor
创建线程池,例如:
- 可以使用
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
进行同步。 - 使用线程安全的集合类,如
ConcurrentHashMap
、CopyOnWriteArrayList
等。 - 使用
Atomic
类,如AtomicInteger
、AtomicBoolean
等,它们利用底层的原子操作保证线程安全。
- 使用
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 多线程编程的理解和应用能力。