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 有助于编写高效、安全的并发程序,避免常见的并发问题。