关于可见性大家还有误区的问题 Thread.sleep对其的影响讨论

generally2008 2020-04-16 11:22:29
本篇我主要是想咨询下Thread.sleep对于工作内存与主内存的变量值同步影响的问题?

对于学习可见性,我们应该都看过下面这样的代码,很简单的

public class VisibilityTest {
Integer a = 0;
public static void main(String[] args) throws InterruptedException {
VisibilityTest test = new VisibilityTest();
test.testVisibility();
}


public void testVisibility() {
new Thread(() -> {
System.out.println("thread 1 start ======= a =" + this.a);
while (this.a != 10) {
}
System.out.println("thred 1 a =======" + this.a);
System.out.println("thread 1 end =======");
}).start();

try {
Thread.sleep(1000L);
// TimeUnit.NANOSECONDS.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
System.out.println("thread 2 modify ....");

this.a = 10;
System.out.println("thread 2 modify success a = " + this.a);
}).start();

}
}


上面这个代码大概率的运行结果会是这样的:

thread 1 start ======= a =0
thread 2 modify ....
thread 2 modify success a = 10

然后main方法一直停止不掉;

那如果我们将Thread.sleep()这个去掉了,即使线程1先运行,但程序依然会执行成功;

public void testVisibility() {
new Thread(() -> {
System.out.println("thread 1 start ======= a =" + this.a);
while (this.a != 10) {
}
System.out.println("thred 1 a =======" + this.a);
System.out.println("thread 1 end =======");
}).start();

new Thread(() -> {
System.out.println("thread 2 modify ....");

this.a = 10;
System.out.println("thread 2 modify success a = " + this.a);
}).start();

}



thread 1 start ======= a =0
thread 2 modify ....
thread 2 modify success a = 10
thred 1 a =======10
thread 1 end =======


那这跟我们所了解的可见性好像有点区别啊?
为什么Thread.sleep()会影响到主内存中的值刷新到线程1中的工作内存中去了?因为线程2运行完了,它会通过原子操作将以前进行assign的值,通过store,write这些原子操作设置到主内存中去;但我加了Thread.sleep(1000L)这行代码时,我运行main方法,程序会等很久,线程1不会完成(也即线程1的工作内存副本一直没有获取到主内存中的最新值);但是我不加这行代码,线程1即使比线程2先运行,它也会很快的刷新到主内存中的值;

那么带着这个疑问,我将方法体中Thread.sleep(1000L)改成了TimeUnit.NANOSECONDS.sleep(1); 我让主线程休眠1纳秒;这时,我测试j时,跟方法体中不加这行代码一样,线程1也会很快的读取到主内存中的值;

上面这些测试都是在windows系统中测试的;由些可见,Thread.sleep会影响主内存与工作副本中的值的刷新时间,而且影响有点大;不知道有相关了解的大佬们能给个合理的解释不?
...全文
605 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
qybao 2020-04-16
  • 打赏
  • 举报
回复
Thread.sleep对变量的可见性没影响,只是影响线程1和线程2的调度而已
如果有Thread.sleep,那线程1会彻底进入while循环,此时应为a是非volatile的,所以线程1的a的值是线程1栈的副本,一直没被刷新,就会死循环。而如果没有Thread.sleep,可能在线程1进入while循环之前可能cpu轮换了,线程2被启动了,先执行了a=10的操作,并刷新了主存,所以cpu再轮换到线程1执行的时候,线程1的a从主存拷贝到自己栈内就是10(最新值),所以没进入while循环,不会导致死循环。
generally2008 2020-04-16
  • 打赏
  • 举报
回复
引用
我觉得这个CPU缓存也有关系,也就是说线程1执行到while (this.a != 10)的时候,还是会从主动从主存获得最新值刷新自己的副本的,但是进入while循环后,每次刷新主存的值如果都一样,达到一定次数后cpu为了提高效率(cpu缓存有优化之类的)可能会把该值存入自己的寄存器(避免频繁刷新主存),这样while就会一直死循环了(有Thread.sleep的时候保证了线程1进入while并频繁获取主存达到一定的次数) 而如果没有Thread.sleep,线程2会很快刷新主存后,可能会导致线程1的while还没达到a被cpu放到寄存器前就获得了新值而退出了while循环,所以没有造成死循环。
为了验证下你的说法,我将程序进行了调整测试下;虽然不能完全证明,但是有一定的道理(虽然不知道底层线程的join方法是否会带来内存影响,但通过java的jdk代码目前看不出。);

Integer a = 0;

    public void testVisibility5() {

        Thread t2 = new Thread(() -> {
            System.out.println("thread 2 modify ....");

            this.a = 10;
            System.out.println("thread 2 modify success a = " + this.a);
        });

        Thread t1 = new Thread(() -> {
            int b = this.a;
            while (this.a != 10) {
                // 值有可能会溢出,暂时不考虑
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("thred 1 a =======" + this.a + " === b =" + b);
            System.out.println("thread 1 end =======");
        });

        t1.start();
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();

    }
加了Thread.sleep()了,线程1也能执行成功了

thread 2 modify ....
thread 2 modify success a = 10
thred 1 a =======10 === b =0
thread 1 end =======
qybao 2020-04-16
  • 打赏
  • 举报
回复
引用 9 楼 generally2008 的回复:
那这个怎么解决了?它没有同步块重新主内存中加载a的值了。在线程1中,b的值就是取a的工作副本的值,但是最后while还是执行成功了,那就说明在while时,工作内存的值被刷新过,但通过目前代码看没有触发的点(就是刷新线程1的工作内存中a变量的值)?


我觉得这个CPU缓存也有关系,也就是说线程1执行到while (this.a != 10)的时候,还是会从主动从主存获得最新值刷新自己的副本的,但是进入while循环后,每次刷新主存的值如果都一样,达到一定次数后cpu为了提高效率(cpu缓存有优化之类的)可能会把该值存入自己的寄存器(避免频繁刷新主存),这样while就会一直死循环了(有Thread.sleep的时候保证了线程1进入while并频繁获取主存达到一定的次数)
而如果没有Thread.sleep,线程2会很快刷新主存后,可能会导致线程1的while还没达到a被cpu放到寄存器前就获得了新值而退出了while循环,所以没有造成死循环。
generally2008 2020-04-16
  • 打赏
  • 举报
回复 1
引用 8 楼 dkwuxiang 的回复:
楼上是正解, 补充一下 Thread.sleep ,println 都会影响到 当前 线程 将副本与主存同步
引用 9 楼 generally2008 的回复:
引用
是在同步块里刷新,但是你传给println的参数是0(是刷新前的值),println同步块打印的是传进来的参数,这个参数是线程取得栈变量副本的值传给println方法的,即使println同步块刷新了副本,传进来的参数也不会变。
你这个解释我了解了,但是我将程序修改了下,跟你解释的就产生冲突了

Integer a = 0;

    public void testVisibility5() {
        new Thread(() -> {
            int b = this.a;
            while (this.a != 10) {
            }
            System.out.println("thred 1 a =======" + this.a + " === b =" + b);
            System.out.println("thread 1 end =======");
        }).start();

        new Thread(() -> {
            System.out.println("thread 2 modify ....");

            this.a = 10;
            System.out.println("thread 2 modify success a = " + this.a);
        }).start();

    }
输出的结果是:

thread 2 modify ....
thread 2 modify success a = 10
thred 1 a =======10 === b =0
thread 1 end =======
那这个怎么解决了?它没有同步块重新主内存中加载a的值了。在线程1中,b的值就是取a的工作副本的值,但是最后while还是执行成功了,那就说明在while时,工作内存的值被刷新过,但通过目前代码看没有触发的点(就是刷新线程1的工作内存中a变量的值)?
能给补充解释下吗?
generally2008 2020-04-16
  • 打赏
  • 举报
回复
引用
是在同步块里刷新,但是你传给println的参数是0(是刷新前的值),println同步块打印的是传进来的参数,这个参数是线程取得栈变量副本的值传给println方法的,即使println同步块刷新了副本,传进来的参数也不会变。
你这个解释我了解了,但是我将程序修改了下,跟你解释的就产生冲突了

Integer a = 0;

    public void testVisibility5() {
        new Thread(() -> {
            int b = this.a;
            while (this.a != 10) {
            }
            System.out.println("thred 1 a =======" + this.a + " === b =" + b);
            System.out.println("thread 1 end =======");
        }).start();

        new Thread(() -> {
            System.out.println("thread 2 modify ....");

            this.a = 10;
            System.out.println("thread 2 modify success a = " + this.a);
        }).start();

    }
输出的结果是:

thread 2 modify ....
thread 2 modify success a = 10
thred 1 a =======10 === b =0
thread 1 end =======
那这个怎么解决了?它没有同步块重新主内存中加载a的值了。在线程1中,b的值就是取a的工作副本的值,但是最后while还是执行成功了,那就说明在while时,工作内存的值被刷新过,但通过目前代码看没有触发的点(就是刷新线程1的工作内存中a变量的值)?
dkwuxiang 2020-04-16
  • 打赏
  • 举报
回复
楼上是正解, 补充一下 Thread.sleep ,println 都会影响到 当前 线程 将副本与主存同步
generally2008 2020-04-16
  • 打赏
  • 举报
回复
引用
是在同步块里刷新,但是你传给println的参数是0(是刷新前的值),println同步块打印的是传进来的参数,这个参数是线程取得栈变量副本的值传给println方法的,即使println同步块刷新了副本,传进来的参数也不会变。
嗯!谢谢,受教了,终于全面了解了这个案例的知识点!我说怎么把线程休眠时间值调小点,成功率就很高了!再次感谢!!!
qybao 2020-04-16
  • 打赏
  • 举报
回复
引用 5 楼 generally2008 的回复:
哦!看来我理解错了!难道不是在同步块中就刷新了吗?println是同步块,那么如果刷新的话,应该是在println中就从主内存中获取到了?嗯!基本清楚了,我再了解下同步块的工作内存与主内存变量同步是在什么时候发生的?

是在同步块里刷新,但是你传给println的参数是0(是刷新前的值),println同步块打印的是传进来的参数,这个参数是线程取得栈变量副本的值传给println方法的,即使println同步块刷新了副本,传进来的参数也不会变。
generally2008 2020-04-16
  • 打赏
  • 举报
回复
引用 3 楼 qybao 的回复:
[quote=引用 2 楼 generally2008 的回复:] [ 我不是在线程1中有sout输出吗?线程1已经先执行sout,输出thread 1 start ======= a =0, 那线程1的工作内存中存储的就是a = 0 这个副本;所以并不是先执行线程2;如果先执行完线程2,我就没疑问了
你知道线程什么时候刷新副本吗?除了volatie,发生同步锁的时候也会刷新副本,恰好println方法就是同步方法。 所以刚开始a=0(副本也是0),把0作为参数传给println打印,打印0后重新获取最新值刷新副本 你可以修改成以下的代码再运行试试就知道了,虽然没有Thread.sleep,但是也不会死循环
public void testVisibility() {
        new Thread(() -> {
            System.out.println("thread 1 start ======= a =" + this.a);
            while (this.a != 10) {
                System.out.println("Thread 1 while ==== a ="+this.a); //原因是println是同步方法,会刷新线程的副本
            }
            System.out.println("thred 1 a =======" + this.a);
            System.out.println("thread 1 end =======");
        }).start();
 
        new Thread(() -> {
            System.out.println("thread 2 modify ....");
 
            this.a = 10;
            System.out.println("thread 2 modify success a = " + this.a);
        }).start();
 
    }
所以,证明了你之前的有Thread.sleep,就是为了保证线程进入while循环,因为while循环里没有同步方法,所有就不会刷新a,所以一直死循环 [/quote] 哦!看来我理解错了!难道不是在同步块中就刷新了吗?println是同步块,那么如果刷新的话,应该是在println中就从主内存中获取到了?嗯!基本清楚了,我再了解下同步块的工作内存与主内存变量同步是在什么时候发生的?
qybao 2020-04-16
  • 打赏
  • 举报
回复
引用 3 楼 qybao 的回复:
所以,证明了你之前的有Thread.sleep,就是为了保证线程进入while循环,因为while循环里没有同步方法,所有就不会刷新a,所以一直死循环

一着急,话也说错了,代码也错了,加了Thread.sleep也不会死循环
public void testVisibility() {
new Thread(() -> {
System.out.println("thread 1 start ======= a =" + this.a);
while (this.a != 10) {
System.out.println("Thread 1 while ==== a ="+this.a); //原因是println是同步方法,会刷新线程的副本
}
System.out.println("thred 1 a =======" + this.a);
System.out.println("thread 1 end =======");
}).start();
try {
Thread.sleep(1000);

} catch (Exception e) {
e.printStackTrace();
}

new Thread(() -> {
System.out.println("thread 2 modify ....");

this.a = 10;
System.out.println("thread 2 modify success a = " + this.a);
}).start();
}
qybao 2020-04-16
  • 打赏
  • 举报
回复
引用 2 楼 generally2008 的回复:
[
我不是在线程1中有sout输出吗?线程1已经先执行sout,输出thread 1 start ======= a =0,
那线程1的工作内存中存储的就是a = 0 这个副本;所以并不是先执行线程2;如果先执行完线程2,我就没疑问了

你知道线程什么时候刷新副本吗?除了volatie,发生同步锁的时候也会刷新副本,恰好println方法就是同步方法。
所以刚开始a=0(副本也是0),把0作为参数传给println打印,打印0后重新获取最新值刷新副本
你可以修改成以下的代码再运行试试就知道了,虽然没有Thread.sleep,但是也不会死循环
public void testVisibility() {
new Thread(() -> {
System.out.println("thread 1 start ======= a =" + this.a);
while (this.a != 10) {
System.out.println("Thread 1 while ==== a ="+this.a); //原因是println是同步方法,会刷新线程的副本
}
System.out.println("thred 1 a =======" + this.a);
System.out.println("thread 1 end =======");
}).start();

new Thread(() -> {
System.out.println("thread 2 modify ....");

this.a = 10;
System.out.println("thread 2 modify success a = " + this.a);
}).start();

}


所以,证明了你之前的有Thread.sleep,就是为了保证线程进入while循环,因为while循环里没有同步方法,所有就不会刷新a,所以一直死循环
generally2008 2020-04-16
  • 打赏
  • 举报
回复
引用 1 楼 qybao 的回复:
Thread.sleep对变量的可见性没影响,只是影响线程1和线程2的调度而已 如果有Thread.sleep,那线程1会彻底进入while循环,此时应为a是非volatile的,所以线程1的a的值是线程1栈的副本,一直没被刷新,就会死循环。而如果没有Thread.sleep,可能在线程1进入while循环之前可能cpu轮换了,线程2被启动了,先执行了a=10的操作,并刷新了主存,所以cpu再轮换到线程1执行的时候,线程1的a从主存拷贝到自己栈内就是10(最新值),所以没进入while循环,不会导致死循环。
我不是在线程1中有sout输出吗?线程1已经先执行sout,输出thread 1 start ======= a =0, 那线程1的工作内存中存储的就是a = 0 这个副本;所以并不是先执行线程2;如果先执行完线程2,我就没疑问了

62,616

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧