首页 技术 正文
技术 2022年11月16日
0 收藏 425 点赞 2,544 浏览 4077 个字

其实是为了填之前的一个坑  在一个多线程的案例中出现了阻塞的情况。 https://www.cnblogs.com/hetutu-5238/p/10477875.html   其中的第二个问题,即多个线程循环顺序打印1,2,3,4

public class Demo2 {    public static  volatile  int index = 0;    public static void main(String[] args) {        ThreadTest t1 = new ThreadTest("线程1" , 0);        ThreadTest t2 = new ThreadTest("线程2" , 1);        ThreadTest t3 = new ThreadTest("线程3" , 2);        ThreadTest t4 = new ThreadTest("线程4" , 3);        t1.start();        t2.start();        t3.start();        t4.start();    }    static class ThreadTest extends Thread {        private int i;        public ThreadTest(String name , int i) {            super(name);            this.i = i;        }        @Override        public void run() {            while ( true ) {                if ( (index & 3) == i ) {                    System.out.println(Thread.currentThread().getName() + ":" + (i+1));                    index++;                }            }        }    }}

  这儿的如果我们把index值的volatile 修饰符去掉,会造成程序卡死的问题。这儿我们会发现并没有死锁的产生条件。打印线程信息后

java内存模型中工作内存并不一定会同步主内存的情况分析

  会发现并没有死锁,而是所有的线程阻塞在了第54行,也就是代码中的if ( (index & 3) == i ),这就很奇怪了,尽管我们没有加volatile关键字。但按理说也只是影响工作内存读取主内存index的值的时间而已,一旦同步了最新的值以后,必定有一个线程能够打印出信息才对。

  所以我改造了下程序,每个线程保存一个index值的成员变量,然后我们在几秒钟后打印每个线程的index值,看下具体值的情况。改造结果如下

public class Demo {    public static    int index = 0;    public static void main(String[] args) {        ThreadTest t1 = new ThreadTest("线程1" , 0);        ThreadTest t2 = new ThreadTest("线程2" , 1);        ThreadTest t3 = new ThreadTest("线程3" , 2);        ThreadTest t4 = new ThreadTest("线程4" , 3);        t1.start();        t2.start();        t3.start();        t4.start();        ScheduledExecutorService sh = Executors.newSingleThreadScheduledExecutor();        sh.scheduleWithFixedDelay(()->{            System.out.println("当前主内存的index:"+index);            System.out.println("t1中的index为"+t1.getK());            System.out.println("t2中的index为"+t2.getK());            System.out.println("t3中的index为"+t4.getK());            System.out.println("t4中的index为"+t4.getK());        },5,5, TimeUnit.SECONDS);    }    static class ThreadTest extends Thread {        private int i;        private int k;        public ThreadTest(String name , int i) {            super(name);            this.i = i;        }        public int getK() {            return k;        }        @Override        public void run() {            while ( true ) {                k = index;                if ( (index & 3) == i ) {                    System.out.println(Thread.currentThread().getName() +":"+index+ ":" + (i));                    index++;                }            }        }    }}

  然后运行结果控制台打印如下

java内存模型中工作内存并不一定会同步主内存的情况分析

  发现index增加到372时就不在增加了,所以线程处于阻塞状态。按理说372 &3 =  0 ,应该由线程1来执行打印语句,这儿的结果t1的index值为369,并没有同步到最新的值。这儿注意每隔5秒打印一次,

而我们的线程的循环中是一直在将index赋给线程的k,说明在循环的10秒里主内存的值都还没有同步到线程的工作内存中。而369并不满足 & 3 = 0 的判断条件,其他线程虽然同步到了最新的值但是自身并不满足判断条件,所以线程就一直处于循环状态。

  到目前可以说已经找到了问题所在了,即工作内存在实验的时间里一直都没有同步主内存的值。而为什么volatile关键字加上就可以呢,这个很好解释,因为这个关键字除了防止指令重排序外,还有的一个重要作用就是保持内存可见性。即volatile修饰的变量 保证某个线程对该变量执行use操作的前一步必须执行load操作加载主内存的值,保证了一定能看到其他线程推送到主内存的最新的值。同时也规定了assign操作后必须立即执行store操作,保证了自己的修改一定能被其他的线程看到。

  所以我们接下来的问题则是为何上面示例中的代码会一直不同步主内存的值了。网上找了下,这个问题在The Java® Language Specification Java SE 7 Edition这本书中的第17.3节中刚好有对类似问题的提及

地址:https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

java内存模型中工作内存并不一定会同步主内存的情况分析

  上面圈红的线则是说Thread.sleep或者yield操作并没有同步语义操作,所以并不一定会在调用前将写操作同步到主内存中,而调用后也不会一定会刷新工作内存寄存器中缓存的值。

  下面则是说明这个this.done只会读一次,而其他循环的时候则会一直从工作内存中的缓存值中读取,即便我们在其他线程中修改了this.done的值,该线程也不会获取到。

  可以写个应用程序实验一下

public class Demo2 {    public static    boolean index = true;     public static int k = 1;    public static void main(String[] args) {        ThreadTest t1 = new ThreadTest("线程1" , 0);        ThreadTest t2 = new ThreadTest("线程2" , 1);        ThreadTest t3 = new ThreadTest("线程3" , 2);        ThreadTest t4 = new ThreadTest("线程4" , 3);        t1.start();        t2.start();        t3.start();        t4.start();        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();        scheduledExecutorService.schedule(()->{            index = false;        },3,TimeUnit.SECONDS);    }    static class ThreadTest extends Thread {        private int i;        public ThreadTest(String name , int i) {            super(name);            this.i = i;        }        @Override        public void run() {            while ( index ) {               k++;            }            System.out.println(Thread.currentThread().getName()+"跳出循环");        }    }}

  上述代码在我的环境中windows+oracle jdk8运行了三次,等待20秒,均没有一个线程打印出 “跳出循环”。确实说明四个线程都是第一次读取到index值之后,就一直没有主动去同步主内存的值,而是一直拿的工作内存中缓存的值。不过在我的代码中实验了多次,在while循环中使用Thread.sleep则每次都会跳出循环,所以我用一个int值k来代替,不知道是否是java8在这块有做什么改动,但我们根据上面的官方文档中以及之前对valotale关键字的总结可以得知,至少这两种情况工作内存是一定会主动去同步主内存的值的:1.如果在线程中使用了valotale关键字修饰变量那么会保证在用到该变量之前从主内存同步值。2.如果线程运行中使用了同步语义也会保证从主内存中同步值。  我们可以实验一下

  1.我们可以实验下只是将K值的修饰符添加volatile ,并不改变index的值,经过我的多次实验,发现确实所有线程都会跳出循环。

java内存模型中工作内存并不一定会同步主内存的情况分析

  2.或者我们可以在循环中添加同步语义也可以让工作内存同步主内存的值,将run中的方法改为如下,会发现如果不添加synchronized块是发生不会跳出循环的情况的。

 public void run() {            while ( index ) {                Object o = new Object();                synchronized (o){                }            }            System.out.println(Thread.currentThread().getName()+"跳出循环");        }

  多次实验结果均是如此,说明这两种情况确实会主动同步。

  至此这个坑算是终于填了。总结起来就是一句话:工作内存有可能会一直使用缓存的值而不会主动同步主内存的值,目前可以保证的是工作内存中如果有使用volatile修饰的变量或者显式的同步语义,是一定会主动同步主内存的值的,当然可能还有待探究的其他情况也会主动同步。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,954
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,479
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,291
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,108
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,740
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,774