多线程面试题

多线程面试题

1. 基础概念

  1. 什么是线程?
    • 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
  2. 线程和进程的区别?
    • 进程是资源分配的基本单位,线程是CPU调度的基本单位。
    • 进程有独立的地址空间,线程共享进程的地址空间。
    • 进程切换开销大,线程切换开销小。
  3. 什么是多线程?
    • 多线程是指在一个进程中同时运行多个线程,每个线程执行不同的任务。
  4. 多线程的优点和缺点?
    • 优点:提高CPU利用率,提高程序响应速度,简化程序结构。
    • 缺点:线程安全问题,调试困难,上下文切换开销。

2. 线程的创建和启动

  1. Java中创建线程的方式有哪些?
    • 继承Thread类。
    • 实现Runnable接口。
    • 实现Callable接口,结合FutureTask使用。
    • 使用线程池。
  2. RunnableCallable的区别?
    • Runnable没有返回值,Callable有返回值。
    • Runnable不能抛出受检异常,Callable可以。
  3. start()run()方法的区别?
    • start()方法会启动一个新线程,并调用run()方法。
    • run()方法只是普通的方法调用,不会启动新线程。

3. 线程的生命周期

  1. 线程的生命周期有哪些状态?
    • 新建(New)
    • 就绪(Runnable)
    • 运行(Running)
    • 阻塞(Blocked)
      • 等待(Waiting)
      • 超时等待(Timed Waiting)
    • 终止(Terminated)
  2. 如何让线程进入等待状态?
    • 使用Object.wait()方法。
    • 使用Thread.sleep()方法。
    • 使用LockSupport.park()方法。

4. 线程同步与锁

  1. 什么是线程安全?
    • 线程安全是指多个线程访问共享资源时,不会出现数据不一致或不可预期的结果。
  2. 如何保证线程安全?
    • 使用synchronized关键字。
    • 使用ReentrantLock
    • 使用volatile关键字。
    • 使用原子类(如AtomicInteger)。
  3. synchronizedReentrantLock的区别?
    • synchronized是关键字,ReentrantLock是类。
    • ReentrantLock支持公平锁和非公平锁,synchronized只支持非公平锁。
    • ReentrantLock可以尝试获取锁、可中断、可超时,synchronized不行。
  4. 什么是死锁?如何避免死锁?
    • 死锁是指多个线程互相持有对方所需的资源,导致所有线程都无法继续执行。
    • 避免死锁的方法:避免嵌套锁、按顺序获取锁、使用超时机制。
  5. volatile关键字的作用?
    • volatile保证变量的可见性,但不保证原子性。
    • 禁止指令重排序。

5. 线程池

  1. 什么是线程池?
    • 线程池是一种管理线程的机制,通过复用线程来减少线程创建和销毁的开销。
  2. Java中如何创建线程池?
    • 使用Executors工厂类创建线程池。
    • 使用ThreadPoolExecutor自定义线程池。
  3. 线程池的核心参数有哪些?
    • 核心线程数(corePoolSize)
    • 最大线程数(maximumPoolSize)
    • 空闲线程存活时间(keepAliveTime)
    • 任务队列(workQueue)
    • 线程工厂(threadFactory)
    • 拒绝策略(RejectedExecutionHandler)
  4. 线程池的拒绝策略有哪些?
    • AbortPolicy:直接抛出异常。
    • CallerRunsPolicy:由调用线程执行任务。
    • DiscardOldestPolicy:丢弃队列中最旧的任务。
    • DiscardPolicy:直接丢弃任务。

6. 并发工具类

  1. CountDownLatch的作用?
    • CountDownLatch允许一个或多个线程等待其他线程完成操作。
  2. CyclicBarrier的作用?
    • CyclicBarrier让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,所有线程才会继续执行。
  3. Semaphore的作用?
    • Semaphore用于控制同时访问特定资源的线程数量。
  4. Exchanger的作用?
    • Exchanger用于两个线程之间交换数据。

7. 线程之间如何通信

1. 共享变量

  • 问题:需要解决线程安全问题(如数据竞争)。
  • 解决方案
    • 使用synchronized关键字。
    • 使用volatile关键字(保证可见性)。使变量在多个线程间可见,但无法保证原子性,
    • 使用原子类(如AtomicInteger)。

2. wait() 和 notify() / notifyAll()

  • wait():使当前线程进入等待状态,并释放锁。
  • notify():唤醒一个等待的线程。
  • notifyAll():唤醒所有等待的线程。
  • 注意:必须在synchronized块中使用。

3. BlockingQueue

  • BlockingQueue是一个线程安全的队列,支持阻塞操作。
  • 当队列为空时,消费者线程会被阻塞;当队列满时,生产者线程会被阻塞。
  • 常见的实现类:ArrayBlockingQueueLinkedBlockingQueue

4. Exchanger

  • Exchanger用于两个线程之间交换数据。

  • 当一个线程调用exchange()方法时,它会等待另一个线程也调用exchange()方法,然后交换数据。

  • 它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这个两个线程通过exchange方法交换数据,如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的中断时成对的线程使用exchange()方法,当有一对线程到达了同步点,就会进行交换数据,因此该工具类的线程对象是成对的。

    import java.util.concurrent.Exchanger;
    
    public class ExchangerDemo {
        public static void main(String[] args) {
            Exchanger<String> exchanger = new Exchanger<>();
    
            new Thread(() -> {
                try {
                    String data = "Data from Thread 1";
                    System.out.println("Thread 1 sending: " + data);
                    String received = exchanger.exchange(data);
                    System.out.println("Thread 1 received: " + received);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
    
            new Thread(() -> {
                try {
                    String data = "Data from Thread 2";
                    System.out.println("Thread 2 sending: " + data);
                    String received = exchanger.exchange(data);
                    System.out.println("Thread 2 received: " + received);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
    
    // 执行打印
    Thread 1 sending: Data from Thread 1
    Thread 1 received: Data from Thread 2
        
    Thread 2 sending: Data from Thread 2
    Thread 2 received: Data from Thread 1
    

5. FutureCallable

  • Callable用于返回结果的线程任务。

  • Future用于获取异步任务的结果。

    import java.util.concurrent.*;
    
    public class FutureDemo {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<String> future = executor.submit(() -> {
                Thread.sleep(1000);
                return "Task completed";
            });
    
            System.out.println("Waiting for result...");
            String result = future.get(); // 阻塞等待结果
            System.out.println("Result: " + result);
    
            executor.shutdown();
        }
    }
    

6. ThreadLocal

  • ThreadLocal为每个线程提供独立的变量副本,避免线程安全问题。

  • 适用于线程间数据隔离的场景。

    public class ThreadLocalDemo {
        private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    
        public static void main(String[] args) {
            new Thread(() -> {
                threadLocal.set(1);
                System.out.println("Thread 1: " + threadLocal.get());
            }).start();
    
            new Thread(() -> {
                threadLocal.set(2);
                System.out.println("Thread 2: " + threadLocal.get());
            }).start();
        }
    }
    
    //执行返回
    Thread 1: 1
    Thread 2: 2
    

7.使用Thread.join()

Thread.join() 是 Java 中用于线程通信的一种简单方式。它的作用是让当前线程等待调用 join() 的线程执行完毕后再继续执行。通过 join(),可以实现线程之间的同步和通信。

public class JoinExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            try {
                Thread.sleep(2000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完毕");
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                Thread.sleep(1000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2执行完毕");
        });

        thread1.start();
        thread2.start();

        try {
            // 主线程等待 thread1 执行完毕
            thread1.join();
            System.out.println("主线程等待 thread1 执行完毕");

            // 主线程等待 thread2 执行完毕
            thread2.join();
            System.out.println("主线程等待 thread2 执行完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程继续执行");
    }
}

//线程1开始执行
//线程2开始执行
//线程2执行完毕
//线程1执行完毕
//主线程等待 thread1 执行完毕
//主线程等待 thread2 执行完毕
//主线程继续执行

Thread.join() 的工作原理

  • 当调用 thread.join() 时,当前线程(通常是主线程)会进入等待状态,直到 thread 执行完毕。
  • 如果 thread 已经执行完毕,join() 会立即返回。
  • 如果 thread 还未启动,join() 会等待 thread 启动并执行完毕。

Thread.join() 的超时机制

  • join() 方法还支持设置超时时间,如果在指定时间内目标线程未执行完毕,当前线程会继续执行。

总结

线程通信的方式有很多种,选择合适的方式取决于具体的应用场景:

  • 如果需要简单的数据共享,可以使用共享变量和锁机制。
  • 如果需要线程间的协调,可以使用wait()/notify()BlockingQueue
  • 如果需要更高级的同步工具,可以使用CountDownLatchCyclicBarrierSemaphoreExchanger等。

8. 高级话题

  1. 什么是CAS操作?
    • CAS(Compare And Swap)是一种无锁算法,通过比较并交换来实现线程安全。
  2. ThreadLocal的作用?
    • ThreadLocal为每个线程提供独立的变量副本,避免线程安全问题。
  3. 什么是AQS(AbstractQueuedSynchronizer)?
    • AQS是Java并发包中用于构建锁和同步器的基础框架。
  4. 如何实现一个自定义的同步器?
    • 继承AbstractQueuedSynchronizer并实现其模板方法。
  5. 什么是Fork/Join框架?
    • Fork/Join框架是Java 7引入的并行任务执行框架,适用于分治算法。

9. 实战问题

  1. 如何实现一个生产者-消费者模型?
    • 使用BlockingQueue实现。
    • 使用wait()notify()实现。
  2. 如何实现一个线程安全的单例模式?
    • 使用双重检查锁定(Double-Checked Locking)。
    • 使用静态内部类实现。
  3. 如何实现一个线程安全的计数器?
    • 使用AtomicInteger
    • 使用synchronizedReentrantLock
  4. 如何实现一个线程安全的LRU缓存?
    • 使用LinkedHashMap并重写removeEldestEntry方法。
    • 使用ConcurrentHashMapReentrantLock
  5. 如何实现一个线程池?
    • 使用ThreadPoolExecutor自定义线程池。

10. 性能调优

  1. 如何优化多线程程序的性能?
    • 减少锁的粒度。
    • 使用无锁数据结构。
    • 合理设置线程池参数。
  2. 如何检测和解决线程死锁?
    • 使用jstack工具检测死锁。
    • 使用ThreadMXBean检测死锁。
  3. 如何监控线程池的状态?
    • 使用ThreadPoolExecutor提供的getPoolSize()getActiveCount()等方法。

11. 其他语言中的多线程

  1. Python中的多线程与多进程的区别?
    • Python中的多线程受GIL(全局解释器锁)限制,适合I/O密集型任务。
    • 多进程适合CPU密集型任务。
  2. Go语言中的Goroutine是什么?
    • Goroutine是Go语言中的轻量级线程,由Go运行时管理。

12. 分布式系统中的多线程

  1. 分布式锁的实现方式有哪些?
    • 基于数据库实现。
    • 基于Redis实现。
    • 基于Zookeeper实现。
  2. 如何保证分布式系统中的线程安全?
    • 使用分布式锁。
    • 使用消息队列。

13. 操作系统中的多线程

  1. 操作系统如何调度线程?
    • 操作系统使用调度算法(如时间片轮转、优先级调度)来分配CPU时间。
  2. 什么是用户级线程和内核级线程?
    • 用户级线程由用户空间的线程库管理,内核不可见。
    • 内核级线程由操作系统内核管理。

14. 并发编程模型

  1. 什么是Actor模型?
    • Actor模型是一种并发编程模型,每个Actor是一个独立的计算单元,通过消息传递进行通信。
  2. 什么是CSP模型?
    • CSP(Communicating Sequential Processes)模型通过通道(Channel)进行线程间通信。

15. 其他

  1. 什么是线程上下文切换?

    • 线程上下文切换是指CPU从一个线程切换到另一个线程时,保存当前线程的状态并加载下一个线程的状态。
  2. 如何减少线程上下文切换?

    • 减少线程数。
    • 使用无锁编程。
    • 使用协程(Coroutine)。
  3. 什么是协程?

    • 协程是一种轻量级的线程,由用户控制调度,避免了线程上下文切换的开销。
  4. 什么是线程饥饿?

    • 线程饥饿是指某些线程长时间得不到CPU时间,导致任务无法执行。
  5. 如何避免线程饥饿?

    • 使用公平锁。
    • 合理设置线程优先级。
  6. 什么是线程优先级?

    • 线程优先级是操作系统调度线程时的一个参考因素,优先级高的线程更容易获得CPU时间。
  7. 如何设置线程优先级?

    • 使用Thread.setPriority()方法设置线程优先级。

日夜颠倒头发少 ,单纯好骗恋爱脑 ,会背九九乘法表 ,下雨只会往家跑 ,搭讪只会说你好 ---- 2050781802@qq.com

×

喜欢就点赞,疼爱就打赏

相册 说点什么 简历