ChatGPT 无法解开的 Java 多线程题

2023-10-20 22:46:50 +08:00
 BarackLee

问题 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");
            }
        });
    }
}
1967 次点击
所在节点    Java
13 条回复
yeqizhang
2023-10-21 00:28:43 +08:00
1 不会,2 不会。主要是单线程池,然后 1 那个线程无限循环一直占着坑不退出,打印 hello 的子线程都进入到队列里去了。
BarackLee
2023-10-21 08:07:07 +08:00
@yeqizhang 实际运行下来代码 2 会疯狂输出"Hello world"
yeqizhang
2023-10-21 08:43:10 +08:00
@BarackLee 打错了,1 不会,2 会……
yeqizhang
2023-10-21 08:54:25 +08:00
你试下 hello()方法的第一行打印线程池队列大小,hello2()方法最后一行也打印
sherlockwoo
2023-10-21 09:18:12 +08:00
这两段代码,都新建了只有一个线程的线程池,代码 1 是提交一个无限循环调用 hello2 的任务,hello2 中递归调用开启新任务,代码 2 是提交一个调用 hello2 的任务。
由于只有一个线程,代码 1 的第一个任务会永远占用这个线程,其他任务一直堆积在任务队列中无法被执行
代码 2 中第一个任务执行完后就结束了,无限打印是因为递归不断提交任务引起的

如果线程池改为 Executors.newFixedThreadPool(2); 那么代码 1 也会输出 "hello world"
soarchen
2023-10-21 09:38:53 +08:00
为了更好地解释这两段代码,我们首先需要了解 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
2023-10-21 10:21:54 +08:00
用 gpt4 解答一下,gpt3 不行的
xausky
2023-10-21 10:39:31 +08:00
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
2023-10-21 12:52:34 +08:00
@sherlockwoo 嗯嗯对的,代码 2 虽然不打印"hello world",但是 ThreadPool 里面的 working queue 会一直增加新的 task. 我本来以为这个代码问题, ChatGPT 3.5 能搞的定. 结果试了三次错误两次.
BarackLee
2023-10-21 12:53:06 +08:00
@dw2693734d 只有 3.5 , 没有开 4 也不会开,😂
BarackLee
2023-10-21 12:55:13 +08:00
@soarchen 这个是 3.5 的回答吗? 我问了三次, 他有两次都是回复错误, 说代码 1,2 都会死锁, 不过有一次答对了,和这个回答一样
BarackLee
2023-10-21 12:55:43 +08:00
@xausky 稳!
dw2693734d
2023-10-21 16:16:07 +08:00
@BarackLee

看下 GPT4 的回复正确不:

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

问题 2:代码 2 可能会打印出 "hello world",但这不是确定的。主要原因是线程池只有一个线程,如果 hello2()和 hello()的递归调用速度非常快,那么同样有可能出现类似代码 1 的问题,导致"hello world"无法打印出来。但如果递归没有无限持续,那么"hello world"会被打印出来。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/983929

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX