跳转至

Java 并发面试问题全解析

简介

在 Java 开发的面试中,并发编程相关的问题是高频考点。Java 并发编程涉及到多线程、锁机制、并发工具类等多个方面,理解和掌握这些知识不仅能帮助开发者通过面试,更能提升实际项目中处理高并发场景的能力。本文将围绕 Java 并发面试常见问题,详细介绍基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效运用 Java 并发编程知识。

目录

  1. 基础概念
    • 进程与线程
    • 并发与并行
    • 线程安全
  2. 使用方法
    • 创建线程的方式
    • 线程同步机制
    • 并发工具类的使用
  3. 常见实践
    • 生产者 - 消费者模式
    • 线程池的使用
  4. 最佳实践
    • 避免死锁
    • 合理使用并发工具类
  5. 小结
  6. 参考资料

基础概念

进程与线程

  • 进程:是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间和系统资源。
  • 线程:是进程中的一个执行单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源。

并发与并行

  • 并发:指多个任务在同一时间段内交替执行。在单核 CPU 系统中,多个线程通过时间片轮转的方式实现并发执行。
  • 并行:指多个任务在同一时刻同时执行。只有在多核 CPU 系统中才能实现真正的并行执行。

线程安全

当多个线程访问同一个共享资源时,如果不进行适当的同步控制,可能会导致数据不一致或其他异常情况。线程安全的代码能够保证在多线程环境下正常运行,不会出现数据竞争等问题。

使用方法

创建线程的方式

继承 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("Runnable is running");
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

实现 Callable 接口

import java.util.concurrent.*;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 2;
    }
}

public class CallableExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable callable = new MyCallable();
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(callable);
        Integer result = future.get();
        System.out.println("Result: " + result);
        executor.shutdown();
    }
}

线程同步机制

synchronized 关键字

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class SynchronizedExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Count: " + counter.getCount());
    }
}

Lock 接口

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class CounterWithLock {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}

public class LockExample {
    public static void main(String[] args) throws InterruptedException {
        CounterWithLock counter = new CounterWithLock();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Count: " + counter.getCount());
    }
}

并发工具类的使用

CountDownLatch

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println("Task is running");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("All tasks are completed");
    }
}

常见实践

生产者 - 消费者模式

import java.util.LinkedList;
import java.util.Queue;

class ProducerConsumerExample {
    private static final Queue<Integer> queue = new LinkedList<>();
    private static final int MAX_SIZE = 5;

    static class Producer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.size() == MAX_SIZE) {
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int item = (int) (Math.random() * 100);
                    queue.add(item);
                    System.out.println("Produced: " + item);
                    queue.notifyAll();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (queue) {
                    while (queue.isEmpty()) {
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int item = queue.poll();
                    System.out.println("Consumed: " + item);
                    queue.notifyAll();
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread producerThread = new Thread(new Producer());
        Thread consumerThread = new Thread(new Consumer());
        producerThread.start();
        consumerThread.start();
    }
}

线程池的使用

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                System.out.println("Task is running on thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executor.shutdown();
    }
}

最佳实践

避免死锁

  • 保持锁的获取顺序一致,避免不同线程以不同的顺序获取锁。
  • 使用定时锁,例如 tryLock 方法,避免线程无限期等待锁。

合理使用并发工具类

根据具体的业务场景选择合适的并发工具类,例如使用 CountDownLatch 来实现线程间的等待,使用 CyclicBarrier 来实现线程的同步等。

小结

本文围绕 Java 并发面试常见问题,详细介绍了基础概念、使用方法、常见实践以及最佳实践。通过学习这些知识,读者可以更好地理解 Java 并发编程,掌握多线程编程的技巧,提高处理高并发场景的能力。在实际开发中,需要根据具体的业务需求合理运用并发编程知识,避免出现线程安全等问题。

参考资料

  • 《Effective Java》
  • 《Java 并发编程实战》
  • Oracle Java 官方文档