首页 > 编程语言 >VarHandle:Java9中保证变量读写可见性、有序性、原子性利器

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器

时间:2023-07-30 10:05:18浏览次数:40  
标签:int VarHandle void public static 有序性 Java9 class



文章目录

  • 一、什么是VarHandle
  • 0、JMM
  • 1、jdk9之前无锁技术的实现
  • 二、VarHandle使用
  • 1、VarHandle 快速上手
  • 2、VarHandle常用方法
  • 3、实战案例1:解决可见性(比volatile轻量)
  • 4、实战案例2:解决指令重排序(比volatile轻量)
  • (1)案例分析: partial ordering
  • (2)案例分析:total ordering


一、什么是VarHandle

0、JMM

JMM内存模型深入详解,探索volatile、synchronized与VarHandle深层次的奥秘

1、jdk9之前无锁技术的实现

JDK9之前, Java 中的无锁技术主要体现在以 AtomicInteger 为代表的的原子操作类,它的底层使用 Unsafe 实现,而Unsafe 的问题在于安全性和可移植性。

此外,volatile 主要使用了 Store-Load 屏障来控制顺序,这个屏障还是太强了,有没有更轻量级的解决方法呢?

二、VarHandle使用

1、VarHandle 快速上手

在 Java9 中引入了 VarHandle,来提供更细粒度的内存屏障,保证共享变量读写可见性、有序性、原子性。提供了更好的安全性和可移植性,替代 Unsafe 的部分功能。

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;

public class TestVarHandle {
	int x; // 共享变量
	static VarHandle X; // 共享变量的操作对象 VarHandle 的创建比较耗时,所以最好不要重复创建
	
	static {
		try {
		    // 初始化VarHandle :需要指定要保护的变量,某个类中的 共享变量名名 共享变量类型
			X = MethodHandles.lookup()
					.findVarHandle(TestVarHandle.class, "x", int.class);
		} catch (NoSuchFieldException | IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
    public static void main(String[] args) {
        TestVarHandle obj = new TestVarHandle();
        X.set(obj, 10); // 设置值
        Object o = X.get(obj); // 获取值
        System.out.println(o);
    }
}

2、VarHandle常用方法

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_System

3、实战案例1:解决可见性(比volatile轻量)

下面的实例,是一个经典案例,循环永远不会停止,具体原因此处就不分析了,就是因为可见性导致的。解决这个问题很简单,将stop用volatile进行标记就可以了。

// 永不终止的循环 - 可见性问题
public class TestInfinityLoop {
    static boolean stop = false; //停止标记

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stop = true; // volatile 的写
        });
        System.out.println("start " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        t.start();
        foo();
    }

    private static void foo() {
        while (true) {
            boolean b = stop; // volatile 的读
            if (b) {
                break;
            }
        }
        System.out.println("end " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
    }
}

jdk9引入的VarHandle ,可以替换volatile,也可以实现共享变量的可见性问题:

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Date;

public class TestVarHandleOpaque {
    static boolean stop;

    static VarHandle STOP;

    static {
        try {
            STOP = MethodHandles.lookup()
                    .findStaticVarHandle(TestVarHandleOpaque.class, "stop", boolean.class);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if ((boolean) STOP.getOpaque()) {
                    break;
                }
            }
            System.out.println("quit " + new Date());
        }).start();

        System.out.println("start " + new Date());
        Thread.sleep(1000);
        STOP.setOpaque(true);
    }
}

4、实战案例2:解决指令重排序(比volatile轻量)

同样的,以下例子可以使用VarHandle解决指令重排序的问题:

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.II_Result;

@JCStressTest
@State
//             r1  r2
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
@Outcome(id = "0, 2", expect = Expect.ACCEPTABLE)
@Outcome(id = "1, 2", expect = Expect.ACCEPTABLE)
@Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING)
public class Test4Possible {
    int a;
    int b;
    @Actor // 线程1
    public void action1(II_Result r) {
        a = 1;
        r.r2 = b;
    }
    @Actor // 线程2
    public void action2(II_Result r) {
        b = 2;
        r.r1 = a;
    }
}
public class TestVarHandle {
    /**
     * 案例1: 演示 VarHandle 可以用来解决 partial ordering 问题
     */
    @JCStressTest
    @Outcome(id = {"0, 1","0, 0","1, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "1, 0", expect = Expect.FORBIDDEN, desc = "INTERESTING")
    @State
    public static class Case1{
        int x;
        int y;
        static VarHandle Y;
        static {
            try {
                Y = MethodHandles.lookup().findVarHandle(Case1.class, "y", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        @Actor
        public void actor1(II_Result r) {
            x = 1;
            Y.setRelease(this, 1);
        }

        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) Y.getAcquire(this);
            r.r2 = x;
        }
    }

    /**
     * 案例2: 演示 VarHandle 能解决 total ordering 的情况
     */
    @JCStressTest
    @Outcome(id = {"1, 0","0, 0","0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "1, 1", expect = Expect.FORBIDDEN, desc = "INTERESTING")
    @State
    public static class Case2{
        int x;
        int y;

        static VarHandle X;
        static VarHandle Y;
        static {
            try {
                X = MethodHandles.lookup().findVarHandle(Case2.class, "x", int.class);
                Y = MethodHandles.lookup().findVarHandle(Case2.class, "y", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        @Actor
        public void actor1(II_Result r) {
            r.r2 = (int) X.getAcquire(this);
            Y.setRelease(this, 1);
        }

        @Actor
        public void actor2(II_Result r) {
            r.r1 = (int) Y.getAcquire(this);
            X.setRelease(this, 1);
        }
    }


    /**
     * 案例3: 演示 VarHandle 不能解决 total ordering 的情况
     */
    @JCStressTest
    @Outcome(id = {"1, 0","1, 1","0, 1"}, expect = Expect.ACCEPTABLE, desc = "ACCEPTABLE")
    @Outcome(id = "0, 0", expect = Expect.ACCEPTABLE_INTERESTING, desc = "INTERESTING")
    @State
    public static class Case3{
        int x;
        int y;

        static VarHandle X;
        static VarHandle Y;
        static {
            try {
                X = MethodHandles.lookup().findVarHandle(Case3.class, "x", int.class);
                Y = MethodHandles.lookup().findVarHandle(Case3.class, "y", int.class);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        @Actor
        public void actor1(II_Result r) {
            Y.setRelease(this, 1);
            r.r2 = (int) X.getAcquire(this);
        }

        @Actor
        public void actor2(II_Result r) {
            X.setRelease(this, 1);
            r.r1 = (int) Y.getAcquire(this);
        }
    }
}

(1)案例分析: partial ordering

case 1,如果 y=1 先发生,那么前面的 Store 屏障会阻止 x=1 排下去,而后面的 Load 屏障会阻止后续的 r.r2=x排上来

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_System_02


如果 y=1 后发生,那么 Store 屏障会阻止 x=1 排下去,而 Load 屏障会阻止后续的 r.r2=x 排上去,同样,无法控制 r.r2=x 和 x=1 的执行顺序(case 2 case 3)还有可能是 x=1 在 r.r1=y 之前,但结果与 case 3 相同

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_开发语言_03


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_共享变量_04

(2)案例分析:total ordering

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_java_05


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_System_06


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_共享变量_07


VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_共享变量_08


case 4,注意看红色的屏障之间的代码仍然可以被重排、蓝色的也一样,因为 case 3 中,红色 Load 屏障只能阻止下面的代码上不来,Store 屏障只能阻止上面的代码下不去,但在此之间,代码顺序无法保证

VarHandle:Java9中保证变量读写可见性、有序性、原子性利器_java_09


标签:int,VarHandle,void,public,static,有序性,Java9,class
From: https://blog.51cto.com/u_13540373/6898592

相关文章

  • Java9-17新特性解读+案例+说明+注意+发展趋势
    前言Java8出来这么多年后,已经成为企业最成熟稳定的版本,相信绝大部分公司用的还是这个版本,但是一眨眼今年Java19都出来了,相信很多Java工程师忙于学习工作对新特性没什么了解,有的话也仅限于某一块。本篇就是博主对自己感觉有用的新特性做了一个案例验证及简要说明,整合起来分享给......
  • volatile是如何保证有序性的?
    为什么需要保证有序性?有如下代码,在inti=a;执行了的情况下,i的值最终会为几?publicclassNoVolatileExample{inta=0;booleanflag=false;publicvoidwriter(){a=1;flag=true;}publicvoidreader(){if(flag......
  • Java9新特性
    在介绍java9之前,我们先来看看java成立到现在的所有版本。  1990年初,最初被命名为Oak;  1995年5月23日,Java语言诞生;  1996年1月,第一个JDK-JDK1.0诞生;  1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入Java技术;  1996年9月,约8.3万个网页应用了Jav......
  • Java9比Java8改进了什么
    1)引入了模块系统,采用模块化系统的应用程序只需要这些应用程序所需的那部分JDK模块,而非是整个JDK框架了,减少了内存的开销。2)引入了一个新的package:java.net.http,里面提供了对Http访问很好的支持,不仅支持Http1.1而且还支持HTTP2。3)引入了jshell这个交互性工具,让Java也可以像脚本......
  • Java并发之原子性、可见性和有序性
    1.原子性1.1原子性的定义原子性:原子性即是一个或者多个操作,要么全程执行,并且执行的过程中不被任何因素打断,要么全部不执行。举个例子会更好理解:就像是我们去银行转账的时候,A给B转1000元,如果A的账户减少了1000之后,那么B的账户一定要增加1000。A的账户减钱,B的账户加钱,这两个操作......
  • java9&10&11
    java9语法改进:接口的私有方法Java8中规定接口中的方法除了抽象方法之外,还可以定义静态方法和默认方法。一定程度上,扩展了接口的功能,此时的接口更像是一个抽象类。在Java9中,接口更加的灵活和强大,连方法的权限修饰符都可以声明为private的了,此时方法将不会称为你对外暴露API的......
  • 多线程篇-线程安全-原子性、可见性、有序性解析
    在程序中使用多线程的目的是什么?1、提高效率,增加任务的吞吐量2、提升CPU等资源的利用率,减少CPU的空转多线程的应用在日常开发中很多,带来了很多的便利,让我们以前研究下在多线程场景中要注意问题吧,一般主要从这三个方面考虑1、原子性2、可见性3、有序性如果不能保证原......
  • 【Java 并发】【五】volatile怎么通过内存屏障保证可见性和有序性
    1 前言这节我们就来看看volatile怎么通过内存屏障保证可见性和有序性。2  保证可见性volatile修饰的变量,在每个读操作(load操作)之前都加上Load屏障,强制从主内存读取最新的数据。每次在assign赋值后面,加上Store屏障,强制将数据刷新到主内存。以volatileintx=0;线程A、B进行......
  • java9 和代码相关的主要新特性
    1.增加了jshell的命令行客户端(相比较其他的稍微有点用处)2.多版本兼容jar(一个项目可以打出来适用于不同jdk版本的jar包)3.集合工厂方法(超有用)--之前创建方式......
  • Java内存模型:Java解决可见性和有序性问题的方案
    Java内存模型并发场景下,可见性/原子性/有序性是并发编程Bug源头,而Java内存模型解决了可见性和有序性问题。Java内存模型定义可见性问题原因是缓存,有序性问题原因是编译......