Java中System.out.println()为何会影响内存可见性?

摘要:
当我运行完之后却发现,代码运行的结果跟我预想的有很大的差别,下面是代码的真正运行结果。于是我稍稍改动了下子线程run方法中的代码,如下所示,改完之后我发现代码的运行结果跟我之前分析的结果却是一样的,代码陷入了死循环中,这样看来问题是出在了这句上。难道说这条打印语句已经影响到了内存可见性吗?

我们先来看段代码:

1 class ThreadVolatileDemo extendsThread{
2     static boolean flag=true;//注意该变量没有被volatile修饰
3 @Override
4     public voidrun() {
5         while(flag){
6             System.out.println("子线程");
7 }
8         System.out.println("子线程结束");
9 }
10 }
11 public classThreadVolatile {
12     public static voidmain(String[] args) {
13         ThreadVolatileDemo tVD=newThreadVolatileDemo();
14 tVD.start();
15         try{
16             Thread.sleep(1000);
17         } catch(InterruptedException e) {
18 e.printStackTrace();
19 }
20         System.out.println("一秒钟一结束");
21         ThreadVolatileDemo.flag=false;//flag变量在一秒后在主线程中被修改为false
22 }
23 }

通过看代码我们可以知道这是一个简单的多线程代码,子线程的run方法也很简单,就是一个单纯的while循环,我们先思考一下这段代码可能的运行结果,看代码可知,flag是一个普通变量,初始值为true,且没有被volatile修饰,也就是说它不具备内存可见性,又因为主线程中修改flag变量是在一秒之后的,然而这时候子线程已经开启了,且已经拥有了自己的本地内存,里面也已经存储了flag变量的副本,因为没有被volatile修饰,不具有可见性,所以子线程就不再和主内存中该变量值有任何关系,而是直接操作在本地内存上的变量值。因此由于子线程开启后flag变量的副本值一直为true,所以子线程就一直陷入在while死循环中出不来。

但是!!! 当我运行完之后却发现,代码运行的结果跟我预想的有很大的差别,下面是代码的真正运行结果。

Java中System.out.println()为何会影响内存可见性?第1张

通过结果可知,代码并没有陷入到循环中,这是为什么呢???

于是我稍稍改动了下子线程run方法中的代码,如下所示,改完之后我发现代码的运行结果跟我之前分析的结果却是一样的,代码陷入了死循环中,这样看来问题是出在了 【System.out.println("子线程");】这句上。难道说这条打印语句已经影响到了内存可见性吗?

1 class ThreadVolatileDemo extendsThread{
2     static boolean flag=true;//注意该变量没有被volatile修饰
3 @Override
4     public voidrun() {
5         while(flag){
6             //System.out.println("子线程");
7             //屏蔽掉while循环中的打印语句
8 }
9         System.out.println("子线程结束");
10 }
11 }

改完之后代码的运行结果:

Java中System.out.println()为何会影响内存可见性?第2张

通过查看println源码,可以发现println语句中有一个上锁的操作:

Java中System.out.println()为何会影响内存可见性?第3张

通过查资料发现,在使用了synchronized上锁这个操作后线程会做以下操作:

1.获得同步锁

2.清空工作内存

3.从主内存中拷贝对象副本到本地内存

4.执行代码(打印语句或加加操作)

5.刷新主内存数据

6.释放同步锁

这也就是System.out.println()为何会影响内存可见性的原因了。

免责声明:文章转载自《Java中System.out.println()为何会影响内存可见性?》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇百思不得其解的"Failed to allocate a managed memory buffer of 268435456 bytes."错误解决SQLServer中服务器角色和数据库角色权限详解下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux基础(15)多线程编程

Linux的内核中没有thread的概念,线程是第三方库libpthread实现的, 和vfork(轻量级进程,只有部分copy)有点像(进程的创建fork会完全copy主进程资源 ,而线程会共享资源,子线程创建新资源时其作用域只在当前子线程,而子线程非新新创建的资源会和创建前的主线程共享这些资源) , 线程和进程的创建在内核里都是系统调用copy_proc...

go语言学习--内核态和用户态(协程)

go中的一个特点就是引入了相比于线程更加轻量级的协程(用户态的线程),那么什么是用户态和内核态呢? 一、什么是用户态和内核态 当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈...

Linux下的sleep()和sched_yield()(转)

阿里四面被问到了这个问题,一脸懵逼,下来也没找到什么阐述这个的文章,就自己查man来对比总结一下吧: sched_yield()的man手册描述如下: DESCRIPTION       sched_yield()  causes  the  calling  thread to relinquish the CPU.  The  thread is mo...

TCMalloc

http://code.google.com/p/gperftools/downloads/list   Tcmalloc通过preload或者直接动态链接的方式对malloc等内存分配和释放函数进行截获并提供服务。Tcmalloc提供接口主要涵盖malloc.h的接口   使用 要使用TCMalloc,只要将tcmalloc通过“-ltcmalloc”链...

使用存储过程(22)

存储过程是数据库开发人员为了使用某一特定的数据库而编写SQL语句集。其他的web应用程序可以调用这些存储过程来访问和操作数据库中的数据,如图: web应用程序可以直接访问数据库,也可以通过存储过程来调用数据库,使用存储过程访问数据库与直接访问数据库,相比有很多优势比如: 假如有一套复杂的SQL语句需要在多个aspx文件中,可以把他们放在一个存储过程,然...

Java多线程学习之任务的创建以及在线程中执行任务

传统的创建任务、驱动任务的方式 1.继承Thread类   通过继承Thead类,并重写run方法,在run方法里面编码具体的任务,调用对象的start方法驱动任务。    public class TestThread extends Thread{ private int count = 5;   //创建介绍String形参的构造器,一般...