JUC应用场景
1. 网页服务器处理并发请求
当一个网页服务器需要处理大量并发请求时,可以使用多线程来提高处理效率。
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class WebServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket clientSocket = serverSocket.accept();
new Thread(new ClientHandler(clientSocket)).start();
}
}
}
class ClientHandler implements Runnable {
private Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try {
// 处理客户端请求
clientSocket.getOutputStream().write("HTTP/1.1 200 OK\r\n\r\nHello, World!".getBytes());
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 文件下载器
多线程下载可以加快大文件的下载速度。
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
public class MultiThreadedDownloader {
public static void main(String[] args) throws Exception {
String fileURL = "http://example.com/file.zip";
int numThreads = 4;
new MultiThreadedDownloader().downloadFile(fileURL, numThreads);
}
public void downloadFile(String fileURL, int numThreads) throws Exception {
URL url = new URL(fileURL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int fileSize = conn.getContentLength();
conn.disconnect();
int partSize = fileSize / numThreads;
RandomAccessFile file = new RandomAccessFile("downloaded_file.zip", "rw");
for (int i = 0; i < numThreads; i++) {
int start = i * partSize;
int end = (i == numThreads - 1) ? fileSize : (start + partSize - 1);
new Thread(new DownloadThread(fileURL, start, end, file)).start();
}
}
}
class DownloadThread implements Runnable {
private String fileURL;
private int start;
private int end;
private RandomAccessFile file;
public DownloadThread(String fileURL, int start, int end, RandomAccessFile file) {
this.fileURL = fileURL;
this.start = start;
this.end = end;
this.file = file;
}
@Override
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(fileURL).openConnection();
String range = "bytes=" + start + "-" + end;
conn.setRequestProperty("Range", range);
conn.connect();
byte[] buffer = new byte[1024];
int bytesRead;
try (RandomAccessFile partFile = new RandomAccessFile(file.getFD(), "rw")) {
partFile.seek(start);
while ((bytesRead = conn.getInputStream().read(buffer, 0, 1024)) != -1) {
partFile.write(buffer, 0, bytesRead);
}
}
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 线程池处理任务
使用线程池管理多线程任务,避免频繁创建和销毁线程带来的开销。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
executor.submit(new Task(i));
}
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
class Task implements Runnable {
private int taskId;
public Task(int id) {
this.taskId = id;
}
@Override
public void run() {
System.out.println("Executing Task " + taskId + " by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed Task " + taskId);
}
}
4. 生产者-消费者模型
在实际业务中,生产者-消费者模型常用于任务调度、消息队列等场景。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
queue.put(i);
System.out.println("Produced: " + i);
Thread.sleep(100); // 模拟生产延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
int value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep(150); // 模拟消费延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5. 并行计算
在数据处理和计算任务中,多线程可以显著提高性能。例如,计算一个大数组的总和。
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
public class ParallelSum {
private static final int THRESHOLD = 10_000;
public static void main(String[] args) {
int[] array = new int[100_000];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
ForkJoinPool forkJoinPool = new ForkJoinPool();
Long sum = forkJoinPool.invoke(new SumTask(array, 0, array.length));
System.out.println("Sum: " + sum);
}
}
class SumTask extends RecursiveTask<Long> {
private int[] array;
private int start;
private int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= ParallelSum.THRESHOLD) {
long sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork();
long rightResult = rightTask.compute();
long leftResult = leftTask.join();
return leftResult + rightResult;
}
}
}
当然可以,下面是一些更多实际业务中应用 Java 多线程技术的场景示例。
6. 定时任务调度
在某些业务场景中,需要定时执行任务,比如定时备份数据库、清理日志等,可以使用多线程来实现定时任务调度。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Executing Task at " + System.currentTimeMillis());
// 5秒后执行任务,每10秒执行一次
scheduler.scheduleAtFixedRate(task, 5, 10, TimeUnit.SECONDS);
}
}
7. 并行数据处理
在大数据处理或数据流处理场景中,使用多线程可以显著提高数据处理速度。
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ParallelDataProcessing {
public static void main(String[] args) {
List<String> data = Arrays.asList("data1", "data2", "data3", "data4", "data5");
ExecutorService executor = Executors.newFixedThreadPool(3);
for (String item : data) {
executor.submit(() -> processItem(item));
}
executor.shutdown();
}
private static void processItem(String item) {
System.out.println("Processing " + item + " by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed " + item);
}
}
8. 异步任务处理
在一些业务场景中,需要执行异步任务以提高系统的响应速度,例如异步处理用户请求等。
import java.util.concurrent.CompletableFuture;
public class AsyncTaskExample {
public static void main(String[] args) {
System.out.println("Main thread: " + Thread.currentThread().getName());
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Async task thread: " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟异步任务处理
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Async task completed");
});
future.thenRun(() -> System.out.println("Continuation task thread: " + Thread.currentThread().getName()));
}
}
9. 多线程计算任务
在一些计算密集型任务中,比如矩阵运算、多项式计算等,可以使用多线程提高计算效率。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiThreadedMatrixMultiplication {
private static final int MATRIX_SIZE = 3;
public static void main(String[] args) {
int[][] matrixA = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
int[][] matrixB = { {9, 8, 7}, {6, 5, 4}, {3, 2, 1} };
int[][] result = new int[MATRIX_SIZE][MATRIX_SIZE];
ExecutorService executor = Executors.newFixedThreadPool(MATRIX_SIZE);
for (int i = 0; i < MATRIX_SIZE; i++) {
for (int j = 0; j < MATRIX_SIZE; j++) {
executor.submit(new MatrixMultiplicationTask(matrixA, matrixB, result, i, j));
}
}
executor.shutdown();
while (!executor.isTerminated()) {}
for (int[] row : result) {
for (int value : row) {
System.out.print(value + " ");
}
System.out.println();
}
}
}
class MatrixMultiplicationTask implements Runnable {
private int[][] matrixA;
private int[][] matrixB;
private int[][] result;
private int row;
private int col;
public MatrixMultiplicationTask(int[][] matrixA, int[][] matrixB, int[][] result, int row, int col) {
this.matrixA = matrixA;
this.matrixB = matrixB;
this.result = result;
this.row = row;
this.col = col;
}
@Override
public void run() {
for (int k = 0; k < matrixA.length; k++) {
result[row][col] += matrixA[row][k] * matrixB[k][col];
}
}
}
10. 实时数据分析
在金融、物联网等领域,需要实时分析数据,可以使用多线程来提高处理速度。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class RealTimeDataAnalysis {
public static void main(String[] args) {
BlockingQueue<String> dataQueue = new LinkedBlockingQueue<>();
Thread producerThread = new Thread(new DataProducer(dataQueue));
Thread consumerThread = new Thread(new DataConsumer(dataQueue));
producerThread.start();
consumerThread.start();
}
}
class DataProducer implements Runnable {
private BlockingQueue<String> queue;
public DataProducer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
String data = "Data-" + i;
queue.put(data);
System.out.println("Produced: " + data);
Thread.sleep(500); // 模拟数据生成延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class DataConsumer implements Runnable {
private BlockingQueue<String> queue;
public DataConsumer(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
String data = queue.take();
System.out.println("Consumed: " + data);
Thread.sleep(1000); // 模拟数据处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
JUC核心知识点
Java 并发工具包(Java Concurrency Utilities,简称 JUC)是 Java 提供的一套强大的并发工具,用于简化多线程编程。JUC 位于 java.util.concurrent 包中,包含了多种并发编程的核心工具和类。下面是 JUC 的一些核心知识点以及如何使用这些工具。
1. 基本线程池和任务执行
1.1 Executor 框架
Executor 框架提供了一种将任务提交与任务执行分离的方法。核心接口是 Executor 和 ExecutorService。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.submit(new Task());
}
executor.shutdown();
}
}
class Task implements Runnable {
@Override
public void run() {
System.out.println("Executing Task by " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task Completed by " + Thread.currentThread().getName());
}
}
2. 同步容器类
2.1 BlockingQueue
BlockingQueue 是一个线程安全的队列,常用于生产者-消费者模型。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
queue.put(i);
System.out.println("Produced: " + i);
Thread.sleep(100); // 模拟生产延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
int value = queue.take();
System.out.println("Consumed: " + value);
Thread.sleep(150); // 模拟消费延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3. 并发工具类
3.1 CountDownLatch
CountDownLatch 用于使一个或多个线程等待其他线程完成一组操作。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int count = 3;
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(new Worker(latch)).start();
}
latch.await(); // 等待所有工作线程完成
System.out.println("All workers are done.");
}
}
class Worker implements Runnable {
private CountDownLatch latch;
public Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("Worker " + Thread.currentThread().getName() + " is working...");
try {
Thread.sleep(2000); // 模拟工作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Worker " + Thread.currentThread().getName() + " done.");
latch.countDown(); // 完成工作,计数减一
}
}
3.2 CyclicBarrier
CyclicBarrier 用于使一组线程互相等待,直到到达某个公共屏障点。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
int count = 3;
CyclicBarrier barrier = new CyclicBarrier(count, () -> System.out.println("All parties have arrived."));
for (int i = 0; i < count; i++) {
new Thread(new Worker(barrier)).start();
}
}
}
class Worker implements Runnable {
private CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
System.out.println("Worker " + Thread.currentThread().getName() + " is working...");
try {
Thread.sleep(2000); // 模拟工作
System.out.println("Worker " + Thread.currentThread().getName() + " reached barrier.");
barrier.await(); // 等待其他线程
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
4. 并发集合类
4.1 ConcurrentHashMap
ConcurrentHashMap 是一个线程安全的哈希表,支持高并发性。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
int index = i;
executor.submit(() -> {
map.put("key" + index, index);
System.out.println("Added: key" + index + " by " + Thread.currentThread().getName());
});
}
executor.shutdown();
while (!executor.isTerminated()) {}
map.forEach((key, value) -> System.out.println(key + ": " + value));
}
}
5. 原子类
5.1 AtomicInteger
AtomicInteger 提供了一种线程安全的方式来更新整数值。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger();
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
int value = counter.incrementAndGet();
System.out.println("Counter: " + value);
});
}
executor.shutdown();
}
}
6. Future 和 Callable
Future 和 Callable 提供了一种异步获取结果的方式。
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;
public class FutureCallableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<Integer> future = executor.submit(new Task());
try {
Integer result = future.get(); // 获取异步任务结果
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(2000); // 模拟计算
return 42;
}
}
7. ReentrantLock
ReentrantLock 是一种比 synchronized 更灵活的锁机制。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Runnable task = example::performTask;
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
}
public void performTask() {
lock.lock();
try {
System.out.println("Locked by " + Thread.currentThread().getName());
Thread.sleep(2000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Unlocked by " + Thread.currentThread().getName());
lock.unlock();
}
}
}
8. 读写锁(ReadWriteLock)
ReadWriteLock 允许多个读线程同时访问,但写线程独占访问。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private int sharedData = 0;
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
Runnable readTask = () -> {
for (int i = 0; i < 5; i++) {
example.readData();
try {
Thread.sleep(100); // 模拟读取操作时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeTask = () -> {
for (int i = 0; i < 5; i++) {
example.writeData(i);
try {
Thread.sleep(100); // 模拟写入操作时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t1 = new Thread(readTask);
Thread t2 = new Thread(readTask);
Thread t3 = new Thread(writeTask);
t1.start();
t2.start();
t3.start();
}
public void readData() {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " Reading: " + sharedData);
} finally {
readWriteLock.readLock().unlock();
}
}
public void writeData(int data) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " Writing: " + data);
sharedData = data;
} finally {
readWriteLock.writeLock().unlock();
}
}
}
9. 线程协作
9.1 Semaphore
Semaphore 用于控制同时访问特定资源的线程数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int MAX_PERMITS = 3;
private final Semaphore semaphore = new Semaphore(MAX_PERMITS);
public static void main(String[] args) {
SemaphoreExample example = new SemaphoreExample();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(example::performTask);
}
executor.shutdown();
}
public void performTask() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " Acquired permit.");
Thread.sleep(2000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " Released permit.");
semaphore.release();
}
}
}
9.2 Exchanger
Exchanger 用于两个线程之间交换数据。
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new Thread(new DataProducer(exchanger)).start();
new Thread(new DataConsumer(exchanger)).start();
}
}
class DataProducer implements Runnable {
private Exchanger<String> exchanger;
public DataProducer(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
try {
String data = "Producer Data";
System.out.println("Produced: " + data);
String receivedData = exchanger.exchange(data);
System.out.println("Received by Producer: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class DataConsumer implements Runnable {
private Exchanger<String> exchanger;
public DataConsumer(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
try {
String data = "Consumer Data";
System.out.println("Produced: " + data);
String receivedData = exchanger.exchange(data);
System.out.println("Received by Consumer: " + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
10. Fork/Join 框架
Fork/Join 框架用于将大任务拆分成多个小任务并行执行。适用于计算密集型任务。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
int[] array = new int[100];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
SumTask task = new SumTask(array, 0, array.length);
int result = forkJoinPool.invoke(task);
System.out.println("Sum: " + result);
}
}
class SumTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 10;
private int[] array;
private int start;
private int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= THRESHOLD) {
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork();
rightTask.fork();
return leftTask.join() + rightTask.join();
}
}
}
总结
JUC 提供了一系列强大的工具和类,帮助开发者更容易地实现高效的并发程序。在实际开发中,选择合适的并发工具和类,并根据业务需求进行优化,可以显著提升应用程序的性能和响应速度。希望以上示例能够帮助你更好地理解和应用 JUC 的核心知识点。
juc面试
在中国互联网公司中,关于 JUC(Java 并发工具包)的面试题往往涉及到多线程编程的基础知识、线程安全问题以及 JUC 提供的各种工具和类的实际应用。以下是一些常见的面试题示例:
1. 基础概念
-
什么是线程安全?如何保证线程安全?
-
线程安全是指多个线程访问共享资源时,不会导致数据不一致或程序异常。可以通过同步机制(如
synchronized关键字、Lock接口)或使用线程安全的类(如ConcurrentHashMap)来保证线程安全。
-
-
什么是死锁?如何避免死锁?
-
死锁是指两个或多个线程相互等待对方释放资源,导致程序无法继续执行。避免死锁的方法包括资源排序法、定时锁尝试以及使用高层次的锁机制(如
ReentrantLock)。
-
2. JUC 工具类
-
解释
CountDownLatch的原理及使用场景。-
CountDownLatch是一个同步辅助类,用于使一个或多个线程等待其他线程完成一组操作。常见使用场景包括并行计算中的阶段同步。
import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { int count = 3; CountDownLatch latch = new CountDownLatch(count); for (int i = 0; i < count; i++) { new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } latch.countDown(); }).start(); } latch.await(); System.out.println("All tasks completed."); } } -
-
解释
CyclicBarrier的原理及使用场景。-
CyclicBarrier是一个同步辅助类,使一组线程相互等待,直到到达某个公共屏障点。常见使用场景包括分段任务的汇总,如多线程分段计算结果的合并。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { public static void main(String[] args) { int count = 3; CyclicBarrier barrier = new CyclicBarrier(count, () -> System.out.println("All parties have arrived.")); for (int i = 0; i < count; i++) { new Thread(() -> { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " reached barrier."); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } } -
-
Semaphore的使用场景及如何实现限流。-
Semaphore用于控制同时访问特定资源的线程数量。常见使用场景包括限流和资源池管理。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreExample { private static final int MAX_PERMITS = 3; private final Semaphore semaphore = new Semaphore(MAX_PERMITS); public static void main(String[] args) { SemaphoreExample example = new SemaphoreExample(); ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executor.submit(example::performTask); } executor.shutdown(); } public void performTask() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " Acquired permit."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + " Released permit."); semaphore.release(); } } } -
3. 线程池
-
线程池的工作原理是什么?常见的线程池有哪些?
-
线程池通过重用已创建的线程来减少线程创建和销毁的开销。常见的线程池包括
FixedThreadPool、CachedThreadPool、ScheduledThreadPool和SingleThreadExecutor。
-
-
如何自定义线程池?
-
可以使用
ThreadPoolExecutor类来自定义线程池,指定核心线程数、最大线程数、空闲线程存活时间、任务队列以及线程工厂和拒绝策略。
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class CustomThreadPool { public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10)); for (int i = 0; i < 10; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName() + " is working."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); } } -
4. 原子类和锁
-
什么是 CAS?CAS 是如何实现线程安全的?
-
CAS(Compare-And-Swap)是一种原子操作,用于实现无锁并发。它通过比较内存中的值和预期值,如果相等则更新为新值,否则继续重试。
-
-
ReentrantLock和synchronized的区别?-
ReentrantLock提供了比synchronized更加灵活的锁机制,支持公平锁、非公平锁以及在同一个线程中可以多次获取锁。synchronized是 Java 内置的同步机制,使用简单但功能有限。
-
5. 并发编程中的经典问题
-
生产者-消费者问题如何解决?
-
可以使用
BlockingQueue、Semaphore或者自定义锁和条件变量来解决生产者-消费者问题。
-
-
哲学家就餐问题如何解决?
-
可以使用
ReentrantLock或者Semaphore来解决哲学家就餐问题,避免死锁和饥饿问题。
-
继续介绍一些关于 JUC 的常见面试题和相关概念,帮助你更好地准备面试。
6. 高级锁机制
6.1 ReadWriteLock 的使用场景和实现原理
-
ReadWriteLock允许多个读线程同时访问,但写线程独占访问,适用于读多写少的场景。 -
ReentrantReadWriteLock是ReadWriteLock的一个实现,提供了readLock和writeLock。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private int sharedData = 0;
public void readData() {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " Reading: " + sharedData);
} finally {
readWriteLock.readLock().unlock();
}
}
public void writeData(int data) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " Writing: " + data);
sharedData = data;
} finally {
readWriteLock.writeLock().unlock();
}
}
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
Runnable readTask = example::readData;
Runnable writeTask = () -> example.writeData(42);
new Thread(readTask).start();
new Thread(writeTask).start();
}
}
6.2 StampedLock 的使用场景和实现原理
-
StampedLock是一种改进的读写锁,提供了更高的并发性能,尤其是在读多写少的场景下。 -
StampedLock提供了乐观读锁,允许无锁读取,只有在检测到数据变化时才进行加锁。
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock stampedLock = new StampedLock();
private int sharedData = 0;
public void readData() {
long stamp = stampedLock.tryOptimisticRead();
int data = sharedData;
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
data = sharedData;
} finally {
stampedLock.unlockRead(stamp);
}
}
System.out.println(Thread.currentThread().getName() + " Reading: " + data);
}
public void writeData(int data) {
long stamp = stampedLock.writeLock();
try {
sharedData = data;
System.out.println(Thread.currentThread().getName() + " Writing: " + data);
} finally {
stampedLock.unlockWrite(stamp);
}
}
public static void main(String[] args) {
StampedLockExample example = new StampedLockExample();
Runnable readTask = example::readData;
Runnable writeTask = () -> example.writeData(42);
new Thread(readTask).start();
new Thread(writeTask).start();
}
}
7. 并发集合类
7.1 ConcurrentHashMap 的实现原理和使用
-
ConcurrentHashMap是线程安全的哈希表,实现了高并发性能。 -
通过分段锁机制,每个桶有自己的锁,从而减少锁的竞争。
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
map.forEach((key, value) -> System.out.println(key + ": " + value));
new Thread(() -> map.put("key3", 3)).start();
new Thread(() -> System.out.println(map.get("key3"))).start();
}
}
7.2 ConcurrentLinkedQueue 的实现原理和使用
-
ConcurrentLinkedQueue是一个基于无锁算法的线程安全队列,适用于高并发场景。 -
使用 CAS 操作保证线程安全和高效性。
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentLinkedQueueExample {
public static void main(String[] args) {
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
queue.add(1);
queue.add(2);
System.out.println("Queue size: " + queue.size());
new Thread(() -> queue.add(3)).start();
new Thread(() -> System.out.println(queue.poll())).start();
}
}
8. 原子类
8.1 AtomicInteger 的使用场景和实现原理
-
AtomicInteger提供了一种线程安全的方式来更新整数值,基于 CAS 实现。 -
常用于计数器和计量器等场景。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.incrementAndGet();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Counter: " + counter.get());
}
}
8.2 AtomicReference 的使用场景和实现原理
-
AtomicReference提供了一种线程安全的方式来更新对象引用。 -
常用于需要原子更新复杂对象的场景。
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample {
public static void main(String[] args) {
AtomicReference<String> reference = new AtomicReference<>("initial");
Runnable task = () -> {
for (int i = 0; i < 10; i++) {
String oldValue = reference.get();
String newValue = oldValue + " updated";
reference.compareAndSet(oldValue, newValue);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value: " + reference.get());
}
}
9. 并发问题解决方案
9.1 生产者-消费者问题的解决方案
-
可以使用
BlockingQueue实现生产者-消费者模式,简化线程间通信和同步。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ProducerConsumerExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
Thread producer = new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
queue.put(i);
System.out.println("Produced: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread consumer = new Thread(() -> {
while (true) {
try {
int value = queue.take();
System.out.println("Consumed: " + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
10. Fork/Join 框架
10.1 Fork/Join 框架的使用场景和实现原理
-
Fork/Join 框架用于将大任务拆分成多个小任务并行执行,适用于计算密集型任务。
-
通过
ForkJoinPool和RecursiveTask或RecursiveAction实现。
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
int[] array = new int[100];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
SumTask task = new SumTask(array, 0, array.length);
int result = forkJoinPool.invoke(task);
System.out.println("Sum: " + result);
}
}
class SumTask extends RecursiveTask<Integer> {
private static final int THRESHOLD = 10;
private int[] array;
private int start;
private int end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= THRESHOLD) {
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
线程池的数量与系统的 CPU 核心数
Java 线程池的数量设置与系统的 CPU 核心数有很大关系。合理设置线程池的大小可以充分利用系统资源,提高程序的并发性能和响应速度。以下是一些关于如何根据 CPU 核心数来设置线程池数量的原则和方法。
1. CPU 密集型任务
CPU 密集型任务:主要依赖于处理器进行大量计算,常见的任务包括复杂计算、图像处理、加密/解密等。
对于 CPU 密集型任务(即主要消耗 CPU 资源的任务),线程数的设置应接近于 CPU 核心数。这样可以避免过多的线程上下文切换,从而提高 CPU 使用效率。
建议线程数公式:
$$
\text{线程数} = \text{CPU 核心数} + 1
$$
其中,多出来的一个线程是为了应对偶尔的 I/O 操作或者其它非 CPU 密集型操作。
2. I/O 密集型任务
IO 密集型任务:主要依赖于输入输出操作,常见的任务包括文件读写、网络通信、数据库操作等。
对于 I/O 密集型任务(即主要消耗 I/O 资源如文件读写、网络通信的任务),由于 I/O 操作等待时间较长,CPU 会有大量空闲时间,因此可以设置更多的线程来充分利用这些空闲时间。
建议线程数公式:
$$
\text{线程数} = \text{CPU 核心数} \times (1 + \frac{\text{等待时间}}{\text{CPU 时间}})
$$
其中,等待时间是指任务在等待 I/O 操作完成的时间,CPU 时间是指任务实际消耗 CPU 资源的时间。
3. 混合型任务
对于同时包含 CPU 密集型和 I/O 密集型操作的混合型任务,需要根据具体情况进行调优。可以通过分析任务的 CPU 和 I/O 比例,参考上述两种类型任务的线程数计算方法,综合确定合适的线程池大小。
4. 实际案例
假设一个 Windows 系统的 CPU 核心数为 8:
-
CPU 密集型任务:建议线程池大小为 (8 + 1 = 9)。
-
I/O 密集型任务:假设等待时间是 CPU 时间的 10 倍,建议线程池大小为
$$
8 \times (1 + \frac{10}{1}) = 88
$$。
5. 使用 Runtime.getRuntime().availableProcessors()
在 Java 中,可以使用 Runtime.getRuntime().availableProcessors() 获取系统的 CPU 核心数,并根据上述原则动态设置线程池大小。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
int cpuCores = Runtime.getRuntime().availableProcessors();
// CPU 密集型任务线程池
ExecutorService cpuThreadPool = Executors.newFixedThreadPool(cpuCores + 1);
// I/O 密集型任务线程池
int ioThreads = cpuCores * (1 + 10); // 假设等待时间是 CPU 时间的 10 倍
ExecutorService ioThreadPool = Executors.newFixedThreadPool(ioThreads);
// 提交任务示例
for (int i = 0; i < 100; i++) {
cpuThreadPool.execute(() -> {
// CPU 密集型任务
performCpuTask();
});
ioThreadPool.execute(() -> {
// I/O 密集型任务
performIoTask();
});
}
cpuThreadPool.shutdown();
ioThreadPool.shutdown();
}
private static void performCpuTask() {
// 模拟 CPU 密集型任务
for (int i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
}
private static void performIoTask() {
// 模拟 I/O 密集型任务
try {
Thread.sleep(100); // 模拟 I/O 等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
6. 监控与调整
实际应用中,线程池大小的设置可能需要根据运行情况进行调整和优化。可以使用监控工具(如 JVisualVM、JProfiler 等)来观察线程池的运行情况,并根据负载和性能表现进行调整。
7. 其它注意事项
-
线程池的类型:根据具体应用场景选择合适的线程池类型(如
FixedThreadPool,CachedThreadPool,ScheduledThreadPool等)。 -
任务队列的类型:根据任务特点选择合适的任务队列(如
LinkedBlockingQueue,ArrayBlockingQueue等)。 -
资源限制:考虑系统的内存、I/O 带宽等资源限制,避免设置过大的线程池导致资源争用和性能下降。
通过合理设置线程池的大小,可以有效提高程序的并发性能和资源利用率。希望这些建议和示例代码能对你有所帮助。
Java对象内存布局和加锁机制
Java 对象内存布局
Java 对象在内存中的布局主要包括三个部分:
-
对象头(Object Header)
-
实例数据(Instance Data)
-
对齐填充(Padding)
1. 对象头(Object Header)
对象头由以下两部分组成:
-
Mark Word: 存储对象的运行时数据,如哈希码(hash code)、GC 分代年龄(GC generational age)、锁状态标志(lock state)、偏向锁(biased locking)标志等。
-
Class Pointer: 指向对象的类元数据(Class Metadata),即对象所属的类。
Mark Word
Mark Word 是一个多功能的字段,在不同状态下包含不同的信息。Mark Word 通常占用 32 位或 64 位空间,取决于使用的是 32 位还是 64 位的 JVM。
-
未锁定(Unlocked)状态:
-
32 位:对象哈希码、GC 分代年龄
-
64 位:对象哈希码、GC 分代年龄
-
-
偏向锁(Biased Locking)状态:
-
32 位:线程 ID、Epoch、偏向锁标志
-
64 位:线程 ID、Epoch、偏向锁标志
-
-
轻量级锁(Lightweight Locking)状态:
-
存储指向栈中锁记录的指针(lock record pointer)
-
-
重量级锁(Heavyweight Locking)状态:
-
存储指向重量级锁(monitor)的指针
-
-
GC 标记(GC Marking)状态:
-
GC 用于标记对象的标记信息
-
Class Pointer
Class Pointer 指向对象的类元数据,即对象所属类的定义。这有助于 JVM 在运行时确定对象的类型信息。
2. 实例数据(Instance Data)
实例数据存储的是对象的实际字段值,包括从父类继承下来的字段。实例数据的排列顺序取决于字段在类中的声明顺序以及 JVM 的实现。
3. 对齐填充(Padding)
对齐填充是为了保证对象的大小是 8 字节的倍数(某些 JVM 要求是 16 字节的倍数),以便于内存对齐和访问效率。
加锁的原理
Java 中的锁机制主要包括以下几种:
-
无锁(No Lock)
-
偏向锁(Biased Locking)
-
轻量级锁(Lightweight Locking)
-
重量级锁(Heavyweight Locking)
1. 无锁(No Lock)
当对象没有被任何线程锁定时,Mark Word 存储对象的哈希码和 GC 分代年龄。这是对象的初始状态。
2. 偏向锁(Biased Locking)
偏向锁的设计是为了优化只有一个线程访问同步块的情况。当一个线程首次获取偏向锁时,Mark Word 中记录该线程的 ID。如果同一个线程再次进入同步块,无需进行同步操作,可以大大减少开销。当另一个线程尝试获取该锁时,偏向锁会被撤销。
3. 轻量级锁(Lightweight Locking)
轻量级锁使用 CAS(Compare-And-Swap)操作来尝试获取锁。如果获取成功,Mark Word 会记录指向线程栈中锁记录的指针。如果获取失败,会膨胀为重量级锁。轻量级锁适用于锁竞争不激烈的场景。
4. 重量级锁(Heavyweight Locking)
重量级锁使用操作系统的互斥量(Mutex)实现,性能开销较大。当锁竞争激烈时,轻量级锁会膨胀为重量级锁,Mark Word 中记录指向重量级锁(monitor)的指针。
总结
Java 对象内存布局中的对象头和锁机制是 JVM 实现高效并发的关键部分。通过理解这些底层原理,可以更好地优化并发程序的性能和稳定性。
Java内存模型
Java 内存模型(Java Memory Model,JMM)是 Java 虚拟机(JVM)中定义的一组规范,旨在解决多线程程序中共享变量访问的可见性和指令重排序问题。JMM 确保了多线程环境下程序的正确性和一致性。
1. 基本概念
内存可见性
内存可见性是指一个线程对共享变量的修改,何时能被其他线程看到。在多线程程序中,每个线程都有自己的工作内存(工作缓存),用于存储共享变量的副本。JMM 确保了在特定的操作顺序下,线程对共享变量的修改能够被其他线程及时看到。
指令重排序
指令重排序是编译器和处理器为了优化程序性能,对指令顺序进行重新排列。在单线程环境中,重排序不会影响程序的正确性,但在多线程环境中,重排序可能导致不可预期的并发问题。JMM 通过 happens-before 规则来约束指令重排序。
2. JMM 的关键原则
happens-before 关系
happens-before 关系定义了操作之间的顺序性和可见性规则。它确保一个操作的结果对另一个操作是可见的。以下是 happens-before 规则的主要内容:
-
程序顺序规则: 在一个线程内,按照程序顺序,前面的操作 happens-before 于后面的操作。
-
监视器锁规则: 对一个锁的解锁 happens-before 于后续对这个锁的加锁。
-
volatile 变量规则: 对一个
volatile变量的写操作 happens-before 于后续对这个volatile变量的读操作。 -
线程启动规则: 一个线程的
Thread.start()调用 happens-before 于这个线程内的所有操作。 -
线程终止规则: 一个线程的所有操作 happens-before 于对这个线程的
Thread.join()的返回。 -
传递性: 如果 A happens-before B,且 B happens-before C,则 A happens-before C。
3. volatile 关键字
volatile 关键字提供了一种轻量级的同步机制,确保变量的更新操作对所有线程可见。使用 volatile 变量时,JMM 保证:
-
对
volatile变量的写操作会立刻刷新到主内存中。 -
对
volatile变量的读操作会从主内存中重新加载最新的值。 -
对
volatile变量的读写操作不会与其他内存操作发生重排序。
4. 锁和同步
锁和同步是 JMM 确保内存可见性和指令顺序的核心机制:
synchronized 关键字
synchronized 关键字用于方法或代码块,确保在同一时刻只有一个线程能够执行同步代码。它隐含了两个 happens-before 规则:
-
对一个锁的解锁 happens-before 于后续对这个锁的加锁。
-
进入一个
synchronized代码块前,必须获得锁,而退出这个代码块时必须释放锁。
Lock 接口
Java 提供了 java.util.concurrent.locks.Lock 接口,比 synchronized 更灵活。常用的实现包括 ReentrantLock 和 ReadWriteLock。这些锁也遵循 happens-before 规则,确保线程间的内存可见性和操作顺序。
5. 内存屏障
内存屏障(Memory Barrier)是一种硬件指令,用于控制指令执行的顺序和内存操作的可见性。JMM 使用内存屏障来实现 volatile 变量的语义和锁的语义。常见的内存屏障有:
-
LoadLoad Barrier: 确保在屏障前的所有读操作都完成后,才能执行屏障后的读操作。
-
StoreStore Barrier: 确保在屏障前的所有写操作都完成后,才能执行屏障后的写操作。
-
LoadStore Barrier: 确保在屏障前的所有读操作都完成后,才能执行屏障后的写操作。
-
StoreLoad Barrier: 确保在屏障前的所有写操作都完成后,才能执行屏障后的读操作。这是最严格的内存屏障。
6. JMM 对指令重排序的限制
JMM 对指令重排序的限制主要通过以下方式实现:
-
编译器在编译阶段对代码进行重排序,但必须遵守 happens-before 规则。
-
处理器在执行阶段对指令进行重排序,但必须遵守内存屏障的约束。
-
Java 内存模型通过对 volatile 变量和同步块的使用来强制内存屏障,确保内存可见性和指令顺序。
7. 实践中的应用
volatile 的使用场景
-
状态标志:用 volatile 变量作为状态标志,确保线程能够及时看到状态的变化。
-
单例模式:使用 volatile 变量来实现线程安全的懒汉式单例模式。
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;
}
}
synchronized 的使用场景
-
同步方法或代码块:在多线程环境中保护共享资源,确保线程安全。
-
经典生产者-消费者模型:使用 synchronized 实现线程间的协调。
class ProducerConsumer {
private final LinkedList<Integer> list = new LinkedList<>();
private final int CAPACITY = 10;
public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (this) {
while (list.size() == CAPACITY) {
wait();
}
list.add(value++);
notify();
}
}
}
public void consume() throws InterruptedException {
while (true) {
synchronized (this) {
while (list.isEmpty()) {
wait();
}
int value = list.removeFirst();
notify();
}
}
}
}
总结
Java 内存模型(JMM)通过定义内存可见性和指令重排序的规则,确保了多线程环境下程序的正确性和一致性。理解 JMM 有助于编写高效、安全的并发程序,避免常见的并发问题。

635

被折叠的 条评论
为什么被折叠?



