有没有大佬看下 Java 多线程的问题

2021-12-29 14:48:28 +08:00
 gosidealone

要求是 3 个线程按顺序打印 abcabc
代码如下:

public class ThreadPrint {

    static int sign = 0;

    static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        new Thread(()->{
            for (int i = 0; i < 5; ) {
                lock.lock();
                if (sign % 3 == 0){
                    System.out.print("a");
                    sign++;
                    i++;
                }
                lock.unlock();
            }
        }).start();

       new Thread(()->{
            for (int i = 0; i < 5;) {
                lock.lock();
                if (sign % 3 == 1){
                    System.out.print("b");
                    sign++;
                    i++;
                }
                lock.unlock();
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 5; ) {
                lock.lock();
                if (sign % 3 == 2){
                    System.out.print("c");
                    sign++;
                    i++;
                }
                lock.unlock();
            }
        }).start();
    }
}

为什么这能按照顺序打印出来呢?
第一个线程 unlock 之后为什么不能 for 循环继续 lock 呢?
继续 lock 的话就不能打印出 5 个 a 了。
然而打印结果是 5 个按顺序的 abc

3065 次点击
所在节点    Java
16 条回复
Jooooooooo
2021-12-29 14:53:49 +08:00
这能打印出 abc 感觉是运气好...
wolfie
2021-12-29 15:02:22 +08:00
这个不就是 用 sign + i 控制,死循环 硬打 abc
falsemask
2021-12-29 15:05:43 +08:00
代码没啥问题吧,sign++操作都是锁的范围里,是线程安全的,所有一个线程拿到锁之后判断 sign % 3,不满足条件就会释放锁,满足 sign % 3 条件的线程一定会被唤醒,所以还是按 abc 顺序输出五次
falsemask
2021-12-29 15:06:15 +08:00
@falsemask typo ,所有=所以
final7genesis
2021-12-29 15:07:12 +08:00
第一个线程有可能继续 lock ,但是 Lock 后 sign 值是 1 啊, 不满足 if 条件, 相当于无效循环, 又释放了 lock
zheng96
2021-12-29 15:07:31 +08:00
for 循环里没有对 i 进行增加,i++放到了 sign%3==0 判断里,所以其他时间是在死循环 i<5 ,直到下一个 sign 符合判断的时候
dooonabe
2021-12-29 15:10:35 +08:00
虽然 sign 没有被声明为 volatile ,但 sign 是在独占锁的范围内发生变化的,所以每个线程都能看到 sign 的正确值
gosidealone
2021-12-29 15:11:22 +08:00
@zheng96
@final7genesis
@falsemask 明白了。。。 谢谢各位
goalidea
2021-12-29 15:42:59 +08:00
实质是靠抢锁来控制 sign++,同时靠 sign 数值保证 i++以达到循环的目的。说实话没有必要,很浪费 cpu 资源,当被不满足 if 块的线程抢到锁,线程就只是空加锁解锁。你的需求应该是线程通信,简单的用 synchronized 加上 wait ,复杂的试用 Lock 和 Condition
AlexLokhart
2021-12-29 15:55:05 +08:00
线程通信用 synchronousQueue ,只有当上一个线程 set 值后,下一个线程才能继续,满足这题; ReentrantLock 默认非公平锁,你这么玩纯粹是运气好才按顺序,然而即使是公平锁,线程 start() 的时机仍然不是你控制的,意味着 lock(),unlock() 的时间点你不能控制,顺序也就无从保证。
jorneyr
2021-12-29 16:33:38 +08:00
最简单的方案是使用 3 个 Semaphore ,第一个线程输出后释放下一个线程的 Semaphore 并且再申请自己的 Semphore 一个资源进行阻塞。
uCharles
2021-12-29 16:35:01 +08:00
看到线程我的脑袋就疼。。。。
gosidealone
2021-12-29 16:50:02 +08:00
@AlexLokhart 不是哦,这不是运气好才按顺序的
gosansam
2021-12-29 17:39:57 +08:00
虽然能按顺序打印 5 次 abc ,但是每个线程获取到锁的次数有可能不一样,这里靠 lock 更新 sign 和循环的 i 的值,初始的时候即使第二个或者第三个线程抢到了锁,也不会影响 sign 值,只有第一个线程抢到了才会输出 a 、更新 sign==1 和自己的 i==1 ,这时只有第二个线程获取到锁才会输出 b 、更新 sign==2 和自己的 i==1 ,同理到 sign==3 时,又只有第一个线程获取到锁才会更新,虽然结果是 5 个 abc ,但如果每次都是不对应的线程获得倒锁,每次执行的时间都不同
dejavuwind
2021-12-30 10:41:00 +08:00
反正就是如果 sign 值不符合条件,就算抢到了锁也不给打印,轮到你了才能打印、sign++
leegoo
2021-12-30 13:43:37 +08:00
我将你这部分代码放到 IDEA 里面,用 JAVC 编译。 发现 for 循环是这样的。
编译前:
for (int i = 0; i < 5; ) {
lock.lock();
if (sign % 3 == 0){
System.out.print("a");
sign++;
i++;
}
lock.unlock();
}
编译后:
for (int i = 0; i < 5; lock.unlock();) {

for(语句 A; 语句 B; 语句 C){
语句 A 在整个循环过程中,只会执行一次;语句 B 必须是布尔类型的表达式(当然也可以不写,如果写就必须是布尔类型表达式),通过该布尔表达式去判断是否继续执行循环体;语句 C 会在每次循环结束后执行,也就是说,循环体执行多少次,语句 C 就会执行多少次。(抄自 https://www.jb51.net/article/157807.htm

根据编译后+jb 网站的猜测。当 A 线程获取到锁之后。B 线程如果需要再获取锁,肯定是需要 A 线程释放锁,B 才有机会的。
但是我的问题是:
1.不管语句 C 是什么情况: 只要有语句 B 返回的是布尔值。 第一次肯定会触发一次循环体的。 那么为什么不管怎么样都是先打印 a 而不是先打印 b or c
2.后续我将 for 循环改成普通的模式 for (int i = 0; i < 5; i++) {
lock 变量改为 static volatile ReentrantLock lock = new ReentrantLock();
sign 改为 static volatile AtomicInteger sign = new AtomicInteger(0);
发现只会打印一次 abc 但是依然无法理解为什么一定是打印 abc 不是 acb cba 等

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

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

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

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

© 2021 V2EX