多线程问题

多线程问题

目录

1、现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

可以用 join()来实现,因为join是Java专门为“等待一个线程执行完毕”而设计的机制,具体有两种方式实现:

第一种:主线程串联启动--外部调度,不并存

t1.start();
t1.join();   // 主线程等 t1 结束
t2.start();
t2.join();   // 主线程等 t2 结束
t3.start();
t3.join();

第二种:被调度线程内部join协同--内部协调,并存

可以让 T2 在 run() 方法里先调用 t1.join(),T3 在 run() 里先调用 t2.join()。这样,即使它们都被启动了,T2 也会一直等到 T1 执行完才继续,T3 同理,等 T2 执行完才继续,保证了 T1 → T2 → T3 的顺序执行。

2、如果扩展到 N 个线程 & 单线程线程池方案

如果任务扩展到 N 个,既要求严格顺序,又不想手动管理一堆 join,可以使用单线程线程池。

单线程线程池就是内部只有一条工作线程的线程池,所有任务被放入同一队列,按提交顺序依次执行,无需任何 join、锁或额外通信。

3、什么是Lock和synchronized

  • synchronized:是 JVM 内置的隐式锁,通过关键字修饰方法或代码块,自动加锁、自动释放。使用简单,但功能有限(只能死等、非公平、只有一个条件)。

  • Lock:是 JDK 提供的显式锁接口(如 ReentrantLock),需要手动 lock()unlock(),但提供了更多高级功能(可中断、超时尝试、公平锁、多条件变量等),控制更灵活。

4、在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?

synchronizedLock 都是 Java 中用于保证多线程访问共享资源安全的同步机制,通俗讲就是“锁”,用来确保同一时刻只有一个线程执行某段代码。

Lock的优势是为读和写分别提供了锁,并发的读,互斥的写,读写互斥,这既保证了读的高并发,又保证了写的完整性。

实现:使用 ReentrantReadWriteLock

  • 读操作:加读锁,多个线程可同时进入。

  • 写操作:加写锁,独占访问,同时阻塞其他读和写。

5、在java中wait和sleep方法的不同?

最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。

6、用Java实现阻塞队列。

用 Java 实现阻塞队列,核心思路是 基于 synchronized + wait/notify

public class BlockingQueue<T>{
    private finall LinkedList<T> queue = new LinkedList<>();
    private finall int capacity;
    public BlockingQueue(int capacity){
        this.capacity = capacity;
    }
     // 放入元素,队列满时阻塞    
     public synchronized void put(T element) throws InterruptedException {        
         while (queue.size() == capacity) {            
             wait();  // 队列满,释放锁并等待        
         }        
         queue.add(element);        
         notifyAll(); // 唤醒等待的线程(可能有等待取元素的线程)    
     }    
     // 取出元素,队列空时阻塞    
     public synchronized T take() throws InterruptedException {        
         while (queue.isEmpty()) {            
             wait();  // 队列空,释放锁并等待        
         }       
         T element = queue.removeFirst();        
         notifyAll(); // 唤醒等待的线程(可能有等待放元素的线程)       
         return element;    
     }
}

7、用Java写代码来解决生产者——消费者问题。

package com.uu;

public class Thread7 {
    private final static String LOCK = "lock";  // 作为锁,用于 synchronized 同步。
    private int count = 0;//盘子中的资源数量
    private final static int FULL = 10; // 盘子中的最大资源数量

    //定义生产者内部类,实现 Runnable。
    class Producer implements Runnable{
       @Override
        public void run(){
           //每个生产者线程反复生产 10 次。
           for (int i = 0; i < 10; i++){
               //使用 LOCK 对象作为同步锁。同一时刻只有一个线程能进入临界区。
               synchronized (LOCK) {
                   //当盘子满了(count == 10)时,生产者不能继续生产,进入等待。
                   while(count==FULL){
                       try{
                           LOCK.wait(); //当前线程释放锁,并进入等待状态,
                       } catch (InterruptedException e) {
                           e.printStackTrace(); //若发生中断,打印堆栈
                       }
                   }
                   //退出 while 时,说明 count < FULL,可以生产。
                   System.out.println("生产者 " + Thread.currentThread().getName() + " 总共有 " + ++count + " 个资源");
                   //唤醒所有等待 LOCK 的线程(包括生产者和消费者)。避免只唤醒同类导致死锁
                   LOCK.notifyAll();
               }
           }
       }
    }

    //消费者
    class Consumer implements Runnable{
        @Override
        public void run(){
            //每个消费者也循环 10 次,与总生产次数匹配。
            for (int i = 0; i < 10; i++) {
                synchronized (LOCK) {
                    //当盘子空了(count == 0)时,消费者不能继续消费,进入等待。
                    while (count == 0) {
                        try {
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("消费者 " + Thread.currentThread().getName() + " 总共有 " + --count + " 个资源");
                    LOCK.notifyAll();
                }
            }
        }
    }
    public static void main(String[] args) {
        Thread7 t = new Thread7();
        //循环 5 次,每次创建一个生产者线程和一个消费者线程,共启动 10 个线程。
        for (int i = 0; i <= 5; i++){
            new Thread(t.new Producer(),"生产者-" + i).start();
            new Thread(t.new Consumer(),"消费者-" + i).start();
        }
    }
}

8、用Java编程一个会导致死锁的程序,你将怎么解决?

破坏死锁条件,即下面四个条件之一:

互斥****条件:资源一次只能被一个线程占用

循环等待条件:存在一组线程 {T0, T1, …, Tn},T0 等待 T1 持有的资源,T1 等待 T2 持有的资源,……,Tn 等待 T0 持有的资源。

不可剥夺条件:已分配的资源不能被强制剥夺,只能由持有者主动释放

请求与保持条件:线程已持有至少一个资源,同时又在等待其他线程持有的资源。

9、什么是竞争条件,你怎样发现和解决竞争

竞争条件是指多个线程同时访问同一个共享数据,并且至少有一个线程会修改该数据。

核心原因:对共享资源的「读 - 改 - 写」操作不是原子性的

如何发现?

  • 代码审查(是否存在共享变量,写操作,同步日志等)

  • 压力测试(次数越多,竞争暴露概率越大)

  • 日志分析

如何解决?

核心思路:让「读 - 改 - 写」操作变成原子性,同一时间只允许一个线程修改共享资源

具体方案:使用 synchronized 关键字,保证同一时间只有一个线程进入执行,实现线程安全。或者Lock锁

10、你将如何使用thread dump(线程快照)?你将如何分析Thread dump?

Thread Dump(线程转储 / 线程快照)是JVM 在某一瞬间生成的所有线程状态快照,包含每个线程的调用栈、锁信息、状态、执行位置等。

它是排查死锁****、线程阻塞、CPU 飙高、服务卡顿、响应慢最核心、最常用的工具。

如何分析:

  • 看线程状态,runnable运行,waiting等待,blocked阻塞,(大量的blocked说明锁竞争严重,有性能瓶颈)

  • 看是否有死锁,thread dump最底部会自动检测死锁

  • 看相同调用栈,如果几十个线程都停在同一行代码,说明有性能问题会IO阻塞

  • 看锁信息,看谁持有锁,谁在等锁(大量等待锁,就是锁瓶颈)

11、为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。

12、Java中你怎样唤醒一个阻塞的线程?

线程阻塞分几种场景,唤醒方式不一样,不能一概而论。

  • Object.wait() 阻塞,notify()或者notifyAll()解决

  • Thread.sleep() 阻塞,等到睡眠时间自然结束或者调用线程.interrupt() 中断休眠,抛 InterruptedException

  • synchronized 抢不到锁 BLOCKED,不能主动唤醒,只能等持有锁的线程执行完、释放锁,JVM 自动调度竞争。

  • 阻塞 IO,调用 socket.close() 关闭连接,IO 阻塞会被抛出异常唤醒

13、wait/notify 为什么必须在同步块里?

防止丢失唤醒、竞争条件,保证可见性和原子性。

14、interrupt 能唤醒 synchronized 阻塞吗?

不能,只能唤醒 wait/sleep/join。

15、在Java中CycliBarriar和CountdownLatch有什么区别?

CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。

16、什么是不可变对象,它对写并发应用有什么帮助?

不可变对象:一旦对象被创建完成后,它的内部状态 / 成员变量****就永远不能被修改

帮助:

  • 不可变对象状态只读、不能修改天生线程安全,无需加锁

  • 避免多线程共享变量的并发问题

  • 可以随意共享、全局缓存、自由传递

  • 因为值永远不变、****hashCode 固定适合做 HashMap 键、并发容器 Key

17、你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的?

  • 竞争条件:

    • 现象:多个线程同时读写共享变量,结果错乱,

    • 解决:优先使用不可变对象,或者加锁

  • 锁竞争严重、系统性能下降:

    • 大量线程 BLOCKED 抢同一把锁,接口变慢、吞吐量低。

    • 读****写锁 ReentrantReadWriteLock 读多写少场景提升并发

  • 死锁****:

    • 两个线程互相持有对方需要的锁,互相等待,程序卡死。

    • 统一锁顺序、加锁超时、借助 thread dump 排查;

18、在java中绿色线程和本地线程区别?

绿色线程是用户态、****JVM 调度、多对一映射,轻量但无法利用多核且易全体阻塞;

本地线程是内核态、****OS 调度、一对一映射,能利用多核、隔离阻塞但开销更大。

19、线程与进程的区别?

进程是资源分配最小单位,相互隔离、开销大、崩溃互不影响;

线程是调度执行最小单位,共享进程资源、开销小、通信简单、一个线程挂掉整个进程就挂。

进程:独立工厂,有自己厂房、设备、资金,互不干扰。

线程:工厂里的工人,共享厂房设备,分工干活,一个工人出事,整个工厂停工。

20、什么是多线程中的上下文切换?

CPU 时间片有限,多个线程轮流抢占 CPU 执行

上下文切换就是 CPU 从一个线程切换到另一个线程时,保存当前线程执行现场、恢复下一个线程现场的过程;有性能开销,线程过多、锁竞争严重都会导致频繁上下文切换,拖慢系统性能。

21、死锁与活锁的区别,死锁与饥饿的区别?

死锁 Deadlock:两个或多个线程互相持有对方需要的锁,互相等锁,全都卡死不动;

活锁 Livelock:线程没有阻塞、一直在运行、一直运行空转,谁也进不了下一步;

饥饿 Starvation:一个线程永远抢不到资源,一直被插队饿死。

死锁活锁区别:一个不动,一个一直再动

死锁饥饿区别:一个至少两个线程互相等,一个单线程一直被欺负

22、Java中用到的线程调度算法是什么?

Java 没有自定义线程调度算法,底层依赖操作系统抢占式、时间片轮转 + 动态优先级调度;Java 线程优先级只是对系统的建议,不能保证绝对生效。

23、 在Java中什么是线程调度?

Java 线程调度就是 JVM + 操作系统 按规则选择就绪线程分配 CPU 执行的过程

采用抢占式调度,配合线程优先级做调度建议,线程的让出、抢占、时间片轮换都由调度机制控制。

24、在线程中你怎么处理不可捕捉异常?

线程内部抛出的未捕获异常,外部主线程无法直接 try-catch 捕获,属于线程不可捕捉异常,会导致线程直接终止;

解决方法:在 run 方法内部加 try-catch (Throwable) 兜底

25、什么是线程组,为什么在Java中不推荐使用?

线程组:Java 把多个线程归为一个组,进行统一管理的容器。

但因其 API 老旧、、功能不好,已被线程池完全替代它的管理能力

以上内容来自参考https://www.cnblogs.com/ming569/p/13723752.html

posted on 2026-05-10 09:49  U~U  阅读(2)  评论(0)    收藏  举报