System.out.println的神奇之处

两个小例子讲解System.out

其实本文章讲解的是System.out.println可能带来的误区

  • volatile与system.out组合产生的误区
  • 主内存与工作内存的“小桥梁“

volatile与system.out组合产生的误区

Volatile关键字大家并不是很陌生,他有两个特性,一个是可见性,第二个就是禁止重排序(具体说明是重排序,感兴趣的话去搜下就有,我这里就不做讲解),但是大家也非常清楚,他并不保证原子性。

下面有个例子就可以说明:
代码如下:

public class VolatileTest {
    public static volatile int race = 0;

    public static final Object obj = new Object();

    public static void increase() {
        race++;
    }

    private static final int THREADS_COUNT = 20;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {                     
            for (int i = 0; i < 10000; i++) {
                                increase();
                    }
                }
            });
            threads[i].start();
        }
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(race);
    }
}

这个例子就是说我启用20个线程并且每个线程循环increase()方法10000次,如果保证原子性的情况下应该输出结果是200000次,但是结果往往不是这个数值(而且不变的)。

上面是从表现的层面上来说明了他不能保证原子性。
如果更深层次层面的去看这个问题:

 0: getstatic     #2
 3: iconst_1
 4: iadd
 5: putstatic     #2

这四个是执行race++的四个指令,就是说当我getstatic拿到值得时候是正确的,但是当我执行iadd指令的时候,其他线程可能已经执行putstatic指令,所以我再iadd的时候,其实值是变小了,从这里也看出他并非保证原子性。

increase();
System.out.println(race);
如果你在increase()方法下加入这句话,就会发现结果不管怎么运行都是200000。

这不是很奇怪么,明明race不能保证原子性,为什么输出确实能保证是线程安全的。

问题就是出在System.out.println()这里,
点击查看他的源码会发现:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
}

会发现每次输出都是被同步加锁,其实读到这,很多读者还是会觉得不对劲,因为加锁的只是输出这句话,race++又没被加锁,按常理会出现值重复的情况,但是结果并非如此。

Jvm虚拟机中有个锁优化原则之一就是“锁粗化”,何为锁粗化,但是如果一系列的连续操作都对同一个对象反 复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥 同步操作也会导致不必要的性能损耗。
所以加了System.ou.println()之后相当于代码变成了:

synchronized (obj) {
    for (int i = 0; i < 10000; i++) {
        increase();                                
    }
}

所以现象可能给初学者带到一种误区,会认为他存在原子性,我用这个例子就是想说明的是volatile的可见性但是不保证原子性。

主内存与工作内存的“小桥梁“

  第二个例子就是关于主内存和工作内存,先看下运行代码:
public class TestBooleanStop implements Runnable{

    public  boolean flag=true;

    @Override
    public void run() {
            while(flag){
                //...
        }
    }
    public static void main(String[] args) throws Exception {
        TestBooleanStop testBooleanStop = new TestBooleanStop();
        Thread t=new Thread(testBooleanStop);
        t.start();
        Thread.sleep(3000);
        testBooleanStop.flag=false;
        Thread.sleep(3000);
        System.out.println(testBooleanStop.flag);

    }
}

这代码很简单,启动一个线程进入死循环,然后修改flag观察线程是否停止并输出flag,但是程序运行结果是flag的确为false,但是程序并没有停止,还是一直在运行。

如果在循环体中放入:i++

int i=0;
while(flag){
 i++;
        }

这样子程序依然是不会停止。

但是你在循环体中加入System.out.println(…)就会发现程序就会停止。

while(flag){
   System.out.println(...);
        }

其实最关键的还是在System.out.println,他每次输出都会清除工作内存去同步主内存,由于我们修改的flag存在在主内存,所以每次输出去同步的话必然会把flag更新到我们的工作内存当中并且停止了程序运行。

而synchronized是做了什么的操作呢
获得同步锁;
1、清空工作内存;
2、从主内存拷贝对象副本到工作内存;
3、执行代码(计算或者输出等);
4、刷新主内存数据;
5、释放同步锁。

所以system.out.println是主内存和工作内存之间的小桥梁。

这是我个人对这些现象的看法和分析,如果大家觉得有更好的角度去分析多多指教,我也会去更正改正。
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
"foreach(system.out::println)" 的意思是对于某个集合中的每一个元素,都执行一次 "system.out::println" 操作,其中 "system.out::println" 表示将括号中的内容打印到控制台上。 例如,在 Java 中,可以使用以下代码对一个整数数组进行遍历,并将每个元素打印到控制台上: ```java int[] arr = {1, 2, 3, 4, 5}; for (int num : arr) { System.out.println(num); } ``` 输出结果为: ``` 1 2 3 4 5 ``` 以上代码中,"foreach" 循环使用了冒号 ":" 来遍历整个数组,将每个元素都赋值给变量 "num",然后将其打印到控制台上。"foreach(system.out::println)" 是一个伪代码,它并没有明确的含义。但是,从语法上来看,它似乎是用于循环迭代某个集合并将其中的元素输出到控制台。 在Java编程语言中,如果想要使用foreach语句来迭代一个集合并将其中的元素输出到控制台,可以使用以下代码: ``` for (Object obj : collection) { System.out.println(obj); } ``` 其中,`collection`表示要迭代的集合对象,`Object`是集合中元素的类型。在循环的每一次迭代中,都会将集合中的一个元素赋值给`obj`变量,然后将该变量的值输出到控制台。"foreach(system.out::println)" 这个语句是一个伪代码,但是可以猜测它的意思是遍历一个集合(collection)中的元素,并且对于每个元素,使用 "system.out::println" 方法将其打印输出到控制台。在Java中,这个语句可以用类似以下的代码实现: ``` List<String> list = Arrays.asList("foo", "bar", "baz"); list.forEach(System.out::println); ``` 这个例子中,我们创建了一个包含三个字符串的列表,并且使用 `forEach` 方法遍历了这个列表中的每个元素。在 `forEach` 方法中,我们使用 `System.out::println` 方法将每个元素打印输出到控制台。"foreach(system.out::println)" 的意思是使用 Java 语言中的 foreach 循环遍历某个集合或数组,并将每个元素作为参数传递给 System.out.println() 方法进行输出。 例如,以下代码片段使用 foreach 循环遍历一个整型数组,并将每个元素输出到控制台: ``` int[] nums = {1, 2, 3, 4, 5}; for (int num : nums) { System.out.println(num); } ``` 输出结果为: ``` 1 2 3 4 5 ``` 这里的 `System.out.println(num)` 将每个元素依次输出到控制台,其中 `System.out` 是 Java 中用于输出的标准输出流,`println` 是输出一个字符串并换行的方法。foreach(system.out::println) 是一段伪代码,它的意思是对于集合中的每个元素,都执行 system.out.println 方法,也就是将每个元素打印到控制台上。在实际的编程语言中,具体的语法和用法可能会有所不同,需要根据具体的语言来确定。foreach(system.out::println) 这段代码看起来像是Java语言中的Lambda表达式,它的作用是将一个集合中的每个元素都输出到控制台。其中,system.out::println表示调用System.out对象的println方法。通常情况下,需要在foreach前面指定要遍历的集合对象,例如: List<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.forEach(System.out::println); 以上代码会将集合中的每个元素依次输出到控制台。"foreach(system.out::println)" 是一个Java语言中的代码片段,意思是对于集合中的每一个元素,使用 System.out.println() 方法打印输出。"foreach(system.out::println)" 的意思是使用 Java 编程语言中的 foreach 循环来遍历某个集合,并对集合中的每个元素执行 "system.out::println" 操作,即将元素输出到控制台。其中的 "system.out" 表示标准输出流,"println" 表示将元素作为字符串输出并自动换行。"foreach(system.out::println)" 的意思是使用一个循环遍历某个集合,对集合中的每个元素都执行 "system.out::println" 的操作,即在控制台上输出该元素。这是一种Java语言中的写法,可以用来遍历数组或集合等容器。这段代码是Java语言中用于循环遍历集合中每个元素,并将其输出到控制台上的语法。 具体来说,"foreach"是Java中的增强型for循环语句,可以简化遍历集合或数组的代码编写。在这段代码中,"system.out::println"是Java 8中引入的方法引用语法,表示调用"System.out.println()"方法将元素输出到控制台上。因此,这段代码的作用是将集合中的每个元素都输出到控制台上,以便于调试或观察程序运行时的状态。 您好,很高兴能在这里为您服务。foreach(system.out::println) 是一个伪代码,它可能表示使用 foreach 循环遍历某个集合(collection),并对集合中的每个元素调用 system.out.println 方法进行输出。在 Java 中,这个伪代码可以表示为以下代码: ``` for (Object obj : collection) { System.out.println(obj); } ``` 其中,collection 是一个集合对象,Object 是集合中元素的类型。这段代码会遍历集合中的每个元素,并对每个元素调用 System.out.println 方法,将其输出到控制台。 您好,我是ChatGPT,很高兴能在这里为您服务。 你好!我是ChatGPT,一个由OpenAI训练的大型语言模型。这是一行Java代码,意思是对某个集合(例如一个数组)中的每个元素执行一个操作,这里的操作是输出到控制台。其中system.out是Java的标准输出流对象,println是输出并换行的方法。因此,这段代码的作用是遍历集合中的每个元素,并输出到控制台上。foreach(system.out::println) 是一个伪代码,可以理解为“对于列表中的每个元素,执行system.out.println操作”。 在Java中,可以使用foreach循环来实现这个操作,示例如下: ``` List<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); for (String str : list) { System.out.println(str); } ``` 这段代码的输出结果为: ``` hello world ``` 其中,for (String str : list) 表示遍历list中的每个元素,并将元素赋值给str变量;System.out.println(str) 表示输出str变量的值。 你好!我可以为你提供帮助吗?"foreach(system.out::println)" 这个语句看起来像是 Java 语言中的语法,它的作用是遍历某个集合(比如数组、列表等)中的元素,并将每个元素都打印输出到控制台。 更具体地说,"system.out::println" 表示调用 Java 标准库中 System 类的 out 静态成员变量,该成员变量代表标准输出流,然后再调用该对象的 println 方法,将传入的参数打印输出到控制台。 因此,"foreach(system.out::println)" 的含义就是遍历某个集合,对于集合中的每个元素,都调用 System 类的 out 变量的 println 方法将其打印输出到控制台。 欢迎使用ChatGPT!我能够理解不同的语言,无论是中文还是其他语言。 欢迎来到ChatGPT!我可以为您提供关于各种主题的帮助,请问您想要了解什么?"foreach(system.out::println)" 的意思是使用foreach循环遍历某个集合(如数组、列表等),并对每个元素执行System.out.println()方法,将其输出到控制台。换句话说,这行代码可以用于输出集合中所有元素的值到控制台。这段代码是Java编程语言中的语法。它表示对一个集合中的每一个元素执行一段操作,其中system.out::println表示输出每一个元素到控制台。可以将它翻译成“对于集合中的每个元素,输出它到控制台”。"foreach(system.out::println)" 是一个类似于Java代码的语句,它的意思是对某个集合中的每个元素执行一次"system.out::println"方法,即在控制台打印出该元素的值。这个代码片段是一个Java语言中的循环语句,它可以用来遍历一个集合(例如一个数组或者列表)中的所有元素,并且对每个元素执行指定的操作,这里是使用系统输出函数 System.out.println() 打印出来。因此,这行代码的意思是:对于集合中的每个元素,都执行一次 System.out.println() 函数来将其打印出来。"foreach(system.out::println)" 是一个代码片段,使用 Java 语言编写。它的作用是将一个列表中的元素逐个输出到控制台。具体实现可以参考下面的示例代码: ``` List<String> list = Arrays.asList("apple", "banana", "orange"); list.forEach(System.out::println); ``` 以上代码中,首先创建了一个包含三个字符串的列表,然后调用 `forEach` 方法,将 `System.out::println` 作为参数传入。这个方法会遍历列表中的每个元素,并将其作为参数传递给 `System.out::println` 方法,实现逐个输出到控制台的效果。"foreach(system.out::println)" 是一段伪代码,大概的意思是对一个集合中的每个元素,都执行一次"system.out::println"操作,即输出该元素。在Java语言中,可以使用以下方式实现该功能: ``` List<String> list = Arrays.asList("apple", "banana", "orange"); list.forEach(System.out::println); ``` 以上代码会输出集合中的每个元素。"foreach(system.out::println)" 是一个伪代码,通常用于描述一个循环迭代的过程,其中对于每个元素,都会执行 "system.out::println" 这个动作。 "system.out::println" 是Java编程语言中输出文本到控制台的语法,可以将括号内的内容输出到屏幕上。因此,"foreach(system.out::println)" 可以被理解为对于迭代的每个元素,都将其输出到控制台上。"foreach(system.out::println)" 是一个Java语言中的语句,它的作用是遍历一个集合中的元素,并对每个元素执行 "system.out::println" 操作,也就是将该元素输出到控制台。这个操作可以用Lambda表达式来实现,例如: ``` List<String> list = Arrays.asList("a", "b", "c"); list.forEach(System.out::println); ``` 这段代码会遍历列表中的每个元素,然后依次输出到控制台。foreach(system.out::println) 是一个伪代码,它的意思是对于集合中的每个元素,都执行一次 system.out.println() 方法,即将该元素打印到控制台上。这是一种常见的在编程语言中遍历集合并输出其中元素的方法。"foreach(system.out::println)" 是一段代码,它的作用是对一个集合中的每个元素执行一次system.out::println操作,即将每个元素打印输出到控制台。"foreach(system.out::println)" 是一个代码片段,使用的是 Java 编程语言。它的作用是遍历一个集合(如数组或列表),并将集合中的每个元素打印到控制台上。其中 "system.out::println" 是一个方法引用,表示调用 System 类的 out 静态成员变量的 println 方法。"foreach(system.out::println)" 这段代码是Java中的语法,意思是对于一个集合中的每一个元素,都执行一遍system.out::println方法。其中,system.out::printlnJava中的标准输出方法,可以将括号中的内容输出到控制台。"foreach(system.out::println)" 是一个Java语言中的语法结构,用于遍历某个集合并对其中的每个元素执行一些操作,这里的操作是将元素输出到控制台。 具体而言,这行代码的意思是将一个集合(或数组)中的所有元素都输出到控制台,使用System.out.println方法实现输出操作。"foreach(system.out::println)" 这段代码看起来像是Java中的语句,意思是将一个数组或者集合中的每个元素都输出到控制台(即标准输出),可以理解为一个循环语句。其中,"system.out::println" 表示输出语句,用于将括号内的内容输出到控制台。这段代码看起来像是Java语言的语法,其中: - foreach是一个关键字,表示对集合中的每个元素执行某个操作; - system.out是一个静态属性,表示标准输出流,通常用来在控制台输出信息; - println是一个方法,表示输出字符串并换行。 因此,这段代码的意思是将集合中的每个元素输出到控制台并换行。这段代码是Java语言的语法,用于遍历一个集合中的元素并输出到控制台。 其中,"foreach"是Java 8引入的一种新的遍历方式,可以用来遍历集合中的元素。"system.out::println"是一个Lambda表达式,表示输出当前元素到控制台。 完整的代码示例如下: ``` List<String> list = Arrays.asList("apple", "banana", "orange"); list.forEach(System.out::println); ``` 以上代码将遍历集合中的元素,并输出到控制台,输出结果如下: ``` apple banana orange ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值