b2科目四模拟试题多少题驾考考爆了怎么补救
b2科目四模拟试题多少题 驾考考爆了怎么补救

物理机多线程并发内存模型,指令重排编译器和内存屏障 (2)

电脑杂谈  发布时间:2021-02-05 23:02:29  来源:网络整理

在单线程方案中,此代码执行没有问题。但是在多线程并发方案中,此代码对于从不同线程创建和获取事物有问题。问题的原因与普通的Double Checked Locking(DCL)10模式相似,也就是说,SomeThing的构造和SomeThing引用在构造中对对象变量的分配可能会重新排序。导致返回在get中构造的不完整的SomeThing对象实例。为了解决这个问题,通常的方法是使用volatile来修改对象字段。与使用同步块相比,此方法避免了重新排序,确保了内存可见性并减少了性能损失。但是,如果使用情况对对象的内存可见性不敏感(不需要线程来写入对象,则对象的新值将立即对下一个读取线程可见),在Intel 64 / IA中-32环境,有更好的解决方案。

根据上一章的内容,我们知道在Intel 64 / IA-32下的写操作之间将没有重新排序,即SomeThing对象的构造与对象中对象的分配之间的顺序。处理器的性可以得到保证。似乎仅使用volatile来避免重新排序是不必要的。但是,Java编译器可能会生成重新排序的指令。但是好消息是Oracle的JDK提供了三种方法:不安全。 putOrderedObject,不安全。 putOrderedInt和Unsafe。 putOrderedLong。执行这三种方法时,JDK将插入StoreStore内存屏障,以避免对写操作进行重新排序。在Intel 64 / IA-32架构中,不需要StoreStore障碍,并且Java编译器将删除StoreStore障碍。与写入volatile变量后执行StoreLoad barrier的巨大开销相比,该方法除了避免了因重新排序而导致的性能损失之外,不会带来其他性能开销。

jvm内存模型_jvm的内存模型堆栈_jvm 内存参数

我们将做一个小实验来比较两者之间的性能差异。一种是使用volatile修改对象成员变量。

 1 public class Container {
 2     public static class SomeThing {
 3         private int status;
 4 
 5         public SomeThing() {
 6             status = 1;
 7         }
 8 
 9         public int getStatus() {
10             return status;
11         }
12     }
13 
14     private volatile  SomeThing object;
15 
16     public void create() {
17         object = new SomeThing();
18     }
19 
20     public SomeThing get() {
21         while (object == null) {
22             Thread.yield(); //不加这句话可能会在此出现无限循环
23         }
24         return object;
25     }
26 }

一种是使用不安全。 putOrderedObject以避免在适当位置重新排序。

 1 public class Container {
 2     public static class SomeThing {
 3         private int status;
 4 
 5         public SomeThing() {
 6             status = 1;
 7         }
 8 
 9         public int getStatus() {
10             return status;
11         }
12     }
13 
14     private SomeThing object;
15 
16     private Object value;
17     private static final Unsafe unsafe = getUnsafe();
18     private static final long valueOffset;
19     static {
20         try {
21             valueOffset = unsafe.objectFieldOffset(Container.class.getDeclaredField("value"));
22         } catch (Exception ex) { throw new Error(ex); }
23     }
24 
25     public void create() {
26         SomeThing temp = new SomeThing();
27         unsafe.putOrderedObject(this, valueOffset, null);    //将value赋null值只是一项无用操作,实际利用的是这条语句的内存屏障
28         object = temp;
29     }
30 
31     public SomeThing get() {
32         while (object == null) {
33             Thread.yield();
34         }
35         return object;
36     }
37 
38 
39     public static Unsafe getUnsafe() {
40         try {
41             Field f = Unsafe.class.getDeclaredField("theUnsafe");
42             f.setAccessible(true);
43             return (Unsafe)f.get(null);
44         } catch (Exception e) {
45         }
46         return null;
47     }
48 }

由于直接调用Unsafe.getUnsafe()需要配置JRE以获得更高的权限,因此我们使用反射来获取Unsafe中的UnSafe以获得可用的Unsafe实例。

unsafe.putOrderedObject(this,valueOffset,null)

此句子仅是借用此句子的功能以防止重新排序,而没有其他作用。

使用以下代码测试两个程序的实际运行时间。在生产环境中长时间运行后,在运行时打开-server和-XX:CompileThreshold = 1可以模拟JIT优化效果。

 1 public static void main(String[] args) throws InterruptedException {
 2     final int THREADS_COUNT = 20;
 3     final int LOOP_COUNT = 100000;
 4 
 5     long sum = 0;
 6     long min = Integer.MAX_VALUE;
 7     long max = 0;
 8     for(int n = 0;n <= 100;n++) {
 9         final Container basket = new Container();
10         List putThreads = new ArrayList();
11         List takeThreads = new ArrayList();
12         for (int i = 0; i < THREADS_COUNT; i++) {
13             putThreads.add(new Thread() {
14                 @Override
15                 public void run() {
16                     for (int j = 0; j < LOOP_COUNT; j++) {
17                         basket.create();
18                     }
19                 }
20             });
21             takeThreads.add(new Thread() {
22                 @Override
23                 public void run() {
24                     for (int j = 0; j < LOOP_COUNT; j++) {
25                         basket.get().getStatus();
26                     }
27                 }
28             });
29         }
30         long start = System.nanoTime();
31         for (int i = 0; i < THREADS_COUNT; i++) {
32             takeThreads.get(i).start();
33             putThreads.get(i).start();
34         }
35         for (int i = 0; i < THREADS_COUNT; i++) {
36             takeThreads.get(i).join();
37             putThreads.get(i).join();
38         }
39         long end = System.nanoTime();
40         long period = end - start;
41         if(n == 0) {
42             continue;    //由于JIT的编译,第一次执行需要更多时间,将此时间不计入统计
43         }
44         sum += (period);
45         System.out.println(period);
46         if(period < min) {
47             min = period;
48         }
49         if(period > max) {
50             max = period;
51         }
52     }
53     System.out.println("Average : " + sum / 100);
54     System.out.println("Max : " + max);
55     System.out.println("Min : " + min);
56 }

在作者的计算机上运行测试,可变方案的结果如下

平均值:62535770

最大:82515000

最低:45161000

unsafe.putOrderedObject方案的运行结果如下

平均值:50746230

最大:68999000

最小值:38038000

从结果可以看出,与volatile方案相比,unsafe.putOrderedObject方案使平均时间消耗减少了18.9%,最大时间消耗减少了16.4%,并且最小时间消耗减少了15.8%。另外,即使在发生写-写重新排序的其他处理器中,由于StoreStore屏障的性能损失小于StoreLoad屏障,因此该方法也是可行的解决方案。但是,再次值得注意的是,该方案不是等效替代易失性语义,而是在特定情况下进行的特殊优化。它仅避免了写-写重排序,但不能保证内存可见性。

###附加了1个实验代码以重现重新排序现象

 1 public class Test {
 2     private static int x = 0, y = 0;
 3     private static int a = 0, b =0;
 4 
 5     public static void main(String[] args) throws InterruptedException {
 6         int i = 0;
 7         for(;;) {
 8             i++;
 9             x = 0; y = 0;
10             a = 0; b = 0;
11             Thread one = new Thread(new Runnable() {
12                 public void run() {
13                     //由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
14                     shortWait(100000);
15                     a = 1;
16                     x = b;
17                 }
18             });
19 
20             Thread other = new Thread(new Runnable() {
21                 public void run() {
22                     b = 1;
23                     y = a;
24                 }
25             });
26             one.start();other.start();
27             one.join();other.join();
28             String result = "第" + i + "次 (" + x + "," + y + ")";
29             if(x == 0 && y == 0) {
30                 System.err.println(result);
31                 break;
32             } else {
33                 System.out.println(result);
34             }
35         }
36     }
37 
38 
39     public static void shortWait(long interval){
40         long start = System.nanoTime();
41         long end;
42         do{
43             end = System.nanoTime();
44         }while(start + interval >= end);
45     }
46 }


本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-354009-2.html

相关阅读
    发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

    • 城崎绘美
      城崎绘美

      这样的言论就应当受到处理

    热点图片
    拼命载入中...