V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
BarackLee
V2EX  ›  Java

ChatGPT 无法解开的 Java 多线程题

  •  
  •   BarackLee · 189 天前 · 1739 次点击
    这是一个创建于 189 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题 1,代码 1 会打印出来" hello world" 吗,为什么? 问题 2,代码 2 会打印出来"hello world " 吗,为什么?

    //代码 1
    public class Main {
        static ExecutorService service = Executors.newSingleThreadExecutor();
    
        public static void main (String[] args) {
            service.execute(()->{
                while (true){
                    hello2();
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    
        private static void hello2() {
            hello();
        }
    
        private static void hello() {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    hello2();
                    System.out.println("hello world");
                }
            });
        }
    }
    
    
    //代码 2
    public class Main {
        static ExecutorService service = Executors.newSingleThreadExecutor();
    
        public static void main (String[] args) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    hello2();
                }
            });
        }
    
        private static void hello2() {
            hello();
        }
    
        private static void hello() {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    hello2();
                    System.out.println("hello world");
                }
            });
        }
    }
    13 条回复    2023-10-21 16:16:07 +08:00
    yeqizhang
        1
    yeqizhang  
       189 天前 via Android
    1 不会,2 不会。主要是单线程池,然后 1 那个线程无限循环一直占着坑不退出,打印 hello 的子线程都进入到队列里去了。
    BarackLee
        2
    BarackLee  
    OP
       189 天前
    @yeqizhang 实际运行下来代码 2 会疯狂输出"Hello world"
    yeqizhang
        3
    yeqizhang  
       189 天前 via Android
    @BarackLee 打错了,1 不会,2 会……
    yeqizhang
        4
    yeqizhang  
       189 天前 via Android
    你试下 hello()方法的第一行打印线程池队列大小,hello2()方法最后一行也打印
    sherlockwoo
        5
    sherlockwoo  
       189 天前
    这两段代码,都新建了只有一个线程的线程池,代码 1 是提交一个无限循环调用 hello2 的任务,hello2 中递归调用开启新任务,代码 2 是提交一个调用 hello2 的任务。
    由于只有一个线程,代码 1 的第一个任务会永远占用这个线程,其他任务一直堆积在任务队列中无法被执行
    代码 2 中第一个任务执行完后就结束了,无限打印是因为递归不断提交任务引起的

    如果线程池改为 Executors.newFixedThreadPool(2); 那么代码 1 也会输出 "hello world"
    soarchen
        6
    soarchen  
       189 天前
    为了更好地解释这两段代码,我们首先需要了解 Java 中`ExecutorService`和线程的工作方式。

    `Executors.newSingleThreadExecutor()`返回一个使用单一工作线程操作的执行程序,可以保证任务按提交的顺序执行。

    #### 代码 1

    1. 主线程开始执行。
    2. 主线程启动一个新的线程执行`service.execute()`内的 Lambda 表达式。
    3. 新线程进入一个无限循环,每次循环都会调用`hello2()`方法,然后线程休眠 1 秒。
    4. `hello2()`方法只是一个简单的封装,实际调用`hello()`方法。
    5. `hello()`方法中,一个新的`Runnable`任务被提交给`ExecutorService`。这意味着它请求`ExecutorService`以后某个时间点运行这个任务。但是请注意,这个执行程序是一个`newSingleThreadExecutor()`,所以它只有一个线程。
    6. 而由于`service.execute()`已经持续占用这个唯一的线程(因为它在一个无限循环中),提交给执行程序的任务从不得到执行机会。
    7. 因此,代码 1 永远不会打印“hello world”。

    #### 代码 2

    1. 主线程开始执行。
    2. 主线程启动一个新线程执行`service.execute()`内的`Runnable`任务。
    3. 这个新线程调用`hello2()`,然后`hello2()`调用`hello()`。
    4. 和上面一样,`hello()`中提交了一个新的`Runnable`任务给`ExecutorService`。
    5. 但这次,由于没有无限循环占用执行器的唯一线程,所以提交的任务会被执行。
    6. 但请注意:新提交的任务在执行时又会调用`hello2()`,这意味着它会重新提交自己。因此,这个任务会无限次地重新提交并打印“hello world”。

    结论:

    问题 1: 代码 1 不会打印“hello world”,因为新提交的任务从未得到执行机会。
    问题 2: 代码 2 会无限次地打印“hello world”,因为任务会不断地重新提交自己并得到执行。
    dw2693734d
        7
    dw2693734d  
       189 天前
    用 gpt4 解答一下,gpt3 不行的
    xausky
        8
    xausky  
       189 天前
    GPT 只是语言模型,不要说这种玩玩扰扰的代码,普通代码都有可能出错。
    然后这个代码为什么这样很好理解,里面其实有一些地方可以简化后也可以实现相同效果
    代码 1
    ```
    static ExecutorService service = Executors.newSingleThreadExecutor();

    public static void main (String[] args) {
    service.execute(()->{
    while (true){
    hello();
    }
    });
    }

    static void hello() {
    service.execute(() -> {
    hello();
    System.out.println("hello world");
    });
    }
    ```
    代码 2
    ```
    static ExecutorService service = Executors.newSingleThreadExecutor();

    public static void main (String[] args) {
    service.execute(()->{
    hello();
    });
    }

    static void hello() {
    service.execute(() -> {
    hello();
    System.out.println("hello world");
    });
    }
    ```

    可以看到只有多一个 while 和 没有 while 的区别,OP 版本的 sleep 和多函数递归属于混淆视听,其实就是 ExecutorService 只有一个线程,同时只能跑一个函数,当有 while 的时候一直不退出导致根本没有机会跑 println
    BarackLee
        9
    BarackLee  
    OP
       189 天前
    @sherlockwoo 嗯嗯对的,代码 2 虽然不打印"hello world",但是 ThreadPool 里面的 working queue 会一直增加新的 task. 我本来以为这个代码问题, ChatGPT 3.5 能搞的定. 结果试了三次错误两次.
    BarackLee
        10
    BarackLee  
    OP
       189 天前
    @dw2693734d 只有 3.5 , 没有开 4 也不会开,😂
    BarackLee
        11
    BarackLee  
    OP
       189 天前
    @soarchen 这个是 3.5 的回答吗? 我问了三次, 他有两次都是回复错误, 说代码 1,2 都会死锁, 不过有一次答对了,和这个回答一样
    BarackLee
        12
    BarackLee  
    OP
       189 天前
    @xausky 稳!
    dw2693734d
        13
    dw2693734d  
       189 天前
    @BarackLee

    看下 GPT4 的回复正确不:

    问题 1:代码 1 不会打印出 "hello world"。因为代码在单线程的线程池( ExecutorService )中递归调用 hello()和 hello2(),这导致线程池中的唯一线程被无限占用,从而新提交到线程池的任务无法执行。

    问题 2:代码 2 可能会打印出 "hello world",但这不是确定的。主要原因是线程池只有一个线程,如果 hello2()和 hello()的递归调用速度非常快,那么同样有可能出现类似代码 1 的问题,导致"hello world"无法打印出来。但如果递归没有无限持续,那么"hello world"会被打印出来。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2713 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 10:52 · PVG 18:52 · LAX 03:52 · JFK 06:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.