有没有大佬看下 Java 多线程的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gosidealone
V2EX    Java

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

  •  
  •   gosidealone 2021-12-29 14:48:28 +08:00 3435 次点击
    这是一个创建于 1387 天前的主题,其中的信息可能已经有所发展或是发生改变。

    要求是 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

    16 条回复    2021-12-30 13:43:37 +08:00
    Jooooooooo
        1
    Jooooooooo  
       2021-12-29 14:53:49 +08:00
    这能打印出 abc 感觉是运气好...
    wolfie
        2
    wolfie  
       2021-12-29 15:02:22 +08:00
    这个不就是 用 sign + i 控制,死循环 硬打 abc
    falsemask
        3
    falsemask  
       2021-12-29 15:05:43 +08:00
    代码没啥问题吧,sign++操作都是锁的范围里,是线程安全的,所有一个线程拿到锁之后判断 sign % 3,不满足条件就会释放锁,满足 sign % 3 条件的线程一定会被唤醒,所以还是按 abc 顺序输出五次
    falsemask
        4
    falsemask  
       2021-12-29 15:06:15 +08:00
    @falsemask typo ,所有=所以
    final7genesis
        5
    final7genesis  
       2021-12-29 15:07:12 +08:00
    第一个线程有可能继续 lock ,但是 Lock 后 sign 值是 1 啊, 不满足 if 条件, 相当于无效循环, 又释放了 lock
    zheng96
        6
    zheng96  
       2021-12-29 15:07:31 +08:00
    for 循环里没有对 i 进行增加,i++放到了 sign%3==0 判断里,所以其他时间是在死循环 i<5 ,直到下一个 sign 符合判断的时候
    dooonabe
        7
    dooonabe  
       2021-12-29 15:10:35 +08:00
    虽然 sign 没有被声明为 volatile ,但 sign 是在独占锁的范围内发生变化的,所以每个线程都能看到 sign 的正确值
    gosidealone
        8
    gosidealone  
    OP
       2021-12-29 15:11:22 +08:00
    @zheng96
    @final7genesis
    @falsemask 明白了。。。 谢谢各位
    goalidea
        9
    goalidea  
       2021-12-29 15:42:59 +08:00
    实质是靠抢锁来控制 sign++,同时靠 sign 数值保证 i++以达到循环的目的。说实话没有必要,很浪费 cpu 资源,当被不满足 if 块的线程抢到锁,线程就只是空加锁解锁。你的需求应该是线程通信,简单的用 synchronized 加上 wait ,复杂的试用 Lock 和 Condition
    AlexLokhart
        10
    AlexLokhart  
       2021-12-29 15:55:05 +08:00
    线程通信用 synchronousQueue ,只有当上一个线程 set 值后,下一个线程才能继续,满足这题; ReentrantLock 默认非公平锁,你这么玩纯粹是运气好才按顺序,然而即使是公平锁,线程 start() 的时机仍然不是你控制的,意味着 lock(),unlock() 的时间点你不能控制,顺序也就无从保证。
    jorneyr
        11
    jorneyr  
       2021-12-29 16:33:38 +08:00
    最简单的方案是使用 3 个 Semaphore ,第一个线程输出后释放下一个线程的 Semaphore 并且再申请自己的 Semphore 一个资源进行阻塞。
    uCharles
        12
    uCharles  
       2021-12-29 16:35:01 +08:00
    看到线程我的脑袋就疼。。。。
    gosidealone
        13
    gosidealone  
    OP
       2021-12-29 16:50:02 +08:00
    @AlexLokhart 不是哦,这不是运气好才按顺序的
    gosansam
        14
    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 ,但如果每次都是不对应的线程获得倒锁,每次执行的时间都不同
    hiwind
        15
    hiwind  
       2021-12-30 10:41:00 +08:00
    反正就是如果 sign 值不符合条件,就算抢到了锁也不给打印,轮到你了才能打印、sign++
    leegoo
        16
    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 等
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3559 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 54ms UTC 10:30 PVG 18:30 LAX 03:30 JFK 06:30
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86