Java多线程并发问题。
public class Test {
    public static void main(String[] args) throws Exception {
        Executor executor = new Executor();
        new Thread(executor::write).start();
        new Thread(executor::read).start();
        Thread.sleep(500000);
    }

    static class Executor {
        private int MAX_TIMES = 10000;
        private boolean hasValue = false;

        void write() {
            for (int i = 0; i < MAX_TIMES; i++) {
                while (hasValue) {}
                System.out.println("Write, " + i);
                hasValue = true;
            }
        }
        void read() {
            for (int i = 0; i < MAX_TIMES; i++) {
                while (!hasValue) {}
                hasValue = false;
                System.out.println("Read,  " + i);
            }
        }
    }
}

我有这么一段代码,原本目的是两个线程交换执行、write线程执行一次,read线程执行一次,但hasValue并不是volatile类型,所以会发生死锁

我有两个问题:
1. 如果读写都是cache line,那为什么不是刚执行就发生死锁,而是执行一会,大概会交替执行100次左右才死锁。
2. 输出结果很奇怪,为什么不是write 0=> read 0 => write 1=>read 1.......这样下去,实际输出结果是这样的

Write, 0
Write, 1
Read,  0
Read,  1
Write, 2
Read,  2
Write, 3
Write, 4
Read,  3
Read,  4
Write, 5
Write, 6
Read,  5
Read,  6
Write, 7
Write, 8
Read,  7
Read,  8
Write, 9
Write, 10
Read,  9
Read,  10
Write, 11
Write, 12

6个回答

因为说起来是多线程其实都是在main线程下的子线程,jvm是一个单进程的,其所有的线程都在其进程下面,这里普及一下,何为多进程,比如windows,你可以听着歌写着代码,这就是多进程,jvm是但进程,其就是一个虚拟机,在其main线程下才会开启多线程,这么说也就是虽然是多线程其实是交替执行的,假如时间的最小单位是1,呢么在1这个点a线程运行,在2这个点就有可能是b线程运行,其本身原理就是交替执行,只不过其交换速度很快,所以你才会觉得是2个线程同时再跑,其实说交替不合适,因为1这个时间点a线程执行,2这个时间点a线程依然可能执行,注意是可能,a,b线程会抢线程资源,谁抢到就执行谁,就是这个意思,所以,当a,b两个线程同时运行,注意这里是同时而不是同步,因为线程是不可能同步的,在一个最小时间点,只可能运行一个线程,假如a虽然先抢到线程资源(比如1这个时间点a抢到),但是他比较点背,在随后的(2,3,4时间点)都是b抢到线程资源,呢么就会出现b虽然是后入线程,但是后来者居上了.所以其本身就没有顺序一说,出现的顺序也是无规则的,你每一次运行,规则都会不一样.

weixin_43253743
weixin_43253743 没问题的代码都会写,甚至不用写,现成的库都有,关键是理解运行原理,好解决奇怪的问题。
大约一年之前 回复
weixin_43253743
weixin_43253743 回复zhangpan_soft: 这里面在讨论JMM的内存模型问题,从这个角度去思考为什么会有这样的结果,跟线程池什么的,还有人说的为什么要这么写,没有关系
大约一年之前 回复
weixin_43253743
weixin_43253743 回复zhangpan_soft: 没有死锁,都在空跑,借用死锁概念描述问题。
大约一年之前 回复
weixin_43253743
weixin_43253743 这个问题本身就是java内存模型的问题, 跟线程调度没关系,为什么之前刷新,之后就不会刷新了?如果像你所说是概率问题,那就不会有死锁问题了
大约一年之前 回复
zhangpan_soft
zhangpan_soft 回复weixin_43253743: 对了,补充一点,其实你的代码并不是死锁,只不过你问的死锁,我就用这个词给你说,其实你的代码两个线程都在跑的,只不过没有进入while仅此而已,原因已经说过了,死锁一定发生在线程线程等待,死锁死锁,你的锁在哪里?所以你的其实不是死锁,只是进入不了while,我觉得你可以做个试验,在while之外,for之内打印一句话,看看是否打印,我敢肯定一定打印.
大约一年之前 回复
zhangpan_soft
zhangpan_soft 回复weixin_43253743: 我最近写poi封装了一个多线程(线程池,没有java的传统线程池),你可以参考下https://blog.csdn.net/zhangpan_soft/article/details/82698817
大约一年之前 回复
zhangpan_soft
zhangpan_soft 回复weixin_43253743: 多线程2大特性,其一是原子性,其二是可见性,在多线程中这两大特性必不可少,缺一不可
大约一年之前 回复
zhangpan_soft
zhangpan_soft 回复weixin_43253743: 你的死锁太简单了,因为没有保证可见性,说到这里其实要牵扯到jvm的内存机制,在jvm内存中使用值得时候,是将值临时放入交换区(或者说内存缓冲区),这时候修改值其实修改的试交换区的值,并不是真实值,就像写io缓冲流的时候为什么要flush()下?flush就是为了刷新缓冲区,我们修改值一样,修改的时候修改的内存交换区(或者说是缓存区),修改完之后,jvm会进行一次类似io流的flush操作,将值写入真实堆空间,虽然这个操作的时间很短暂,但是也是耗时,在这个时候另外一个线程进来了,拿的是之前的值,因为你虽然修改了,但是还没有等到jvm刷新交换区(其实不是刷新,类似而已,为了说的清楚),这时候就会拿到之前的值,然后交换区刷新,值被修改,之前线程拿到改变之后的值,就会出现flag=true,!flag=true,的现象,或者说flag=false,!flag=false的现象,这时候,双方都在等待,就死了,至于你说的为什么出现100多回合才死锁,呢是因为这个刷新交换区的时间很短暂,其属于偶然中的必然概率,
大约一年之前 回复
weixin_43253743
weixin_43253743 先明白这代码为什么会死锁吧
大约一年之前 回复

首先代码执行分为 while判断 、空代码块儿执行,hasValue 的赋值 和 System.out.println 四个个步骤看
问题一:个人认为不是死锁,应该是一个线程长时间持有cpu,在进行无限循环执行空代码块儿
问题二:是由于
1,当 write 线程 抢到cup时,执行了 输出和赋值操作后,被read线程抢到cpu
2,read线程抢到cpu, 执行了hasValue 赋值,之后又被write抢到cpu
3, write 线程 抢到cup后,又执行了输出操作
所以看到两次write线程的输出,同理read线程的输出也可以理解

weixin_43253743
weixin_43253743 跟你说的没关系,试试把hasValue 改成volatile。
大约一年之前 回复

如果读写都是cache line,那为什么不是刚执行就发生死锁,而是执行一会,大概会交替执行100次左右才死锁。
2. 输出结果很奇怪,为什么不是write 0=> read 0 => write 1=>read 1.......这样下去,实际输出结果是这样的

第二个问题println不是输出换行吗,那当然是每次循环都换行输出,我不是很理解你为什么会认为会那样子输出

weixin_43253743
weixin_43253743 先明白这代码为什么会死锁吧
大约一年之前 回复

不客气的说 这是我见过最奇葩多线程测试代码,为什么这么写? 抱歉哈

weixin_43253743
weixin_43253743 为了理解原理
大约一年之前 回复
  void write() {
            for (int i = 0; i < MAX_TIMES; i++) {
                while (hasValue) {}
                System.out.println("Write, " + i);
                hasValue = true;
            }
        }
        void read() {
            for (int i = 0; i < MAX_TIMES; i++) {
                while (!hasValue) {}
                hasValue = false;
                System.out.println("Read,  " + i);
            }
        }
            执行次数过多的情况下,两个线程可能同时拥有 while(true),可能不进入最后的一个睡眠状态,造成死锁。
weixin_43253743
weixin_43253743 跟你说的没关系,试试把hasValue 改成volatile。
大约一年之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!