概述
并发
什么时候 会有并发问题
原来入参为 0, 这时 CPU 01 刚刚加一变为为1, 而CPU 02 往内存中写入2,之后变为
本来应该为3
一句话 是因为 穿插执行导致的问题 (非原子性: 要么全都不执行,要么全都执行,在一个同一个事务中)
说到这 你会想到,原子性操作,那原子性操作有哪些?
原子性操作有哪些?
java 基础命令 比如赋值,一行基础代码 对么?
并不是,在Java中,32位或更小数量的读写保证是原子的。原子,意思是每个动作都在一个步骤中发生,不能被打断。因此,当我们有多线程应用程序时,读写操作是线程安全的,不需要进行同步。
比如 i++ 这种一行的基础代码 里面分为几步执行,也不能保证 原子性
既然i++ 不是原子性,那有原子性操作么?
有,AtomicInteger 就是原子类,那怎么证明他的操作是原子的,怎么保证的原子呢?
有的时候 我想 让jar包中的类 不给业务系统使用,我怎么办?
有的时候 我们写jar包的组件,希望业务系统 引用了我们的jar ,但是别使用某个类(这里叫A类),A类 只是给我们自己组件用的。
Unsafe 类就是这样,我们在外部使用的时候提示,在rt.jar 中
- 先调用 public native int getIntVolatile(Object o, long offset); 获取当前的内存中的 旧值 (这里叫 old)
- 循环调用 while (!compareAndSwapInt(o, offset, v, v + delta));
public static Unsafe getUnsafe() {
//返回的是 你调用Unsafe类的 类名 class org.apache.rocketmq.client.test.B
Class<?> caller = Reflection.getCallerClass();
// 返回 sun.misc.Launcher$AppClassLoader
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
// 只要 有ClassLoader 就代表 不是系统的 就会抛出错误
throw new SecurityException("Unsafe");
return theUnsafe;
}
这里会有几个疑问
1 sun.misc.Launcher$AppClassLoader 怎么看着眼熟呢 这代表什么
没错 就是 双亲委派机制,类加载器,ExtentionClassLoader是AppclassLoader的父加载器。
加载器的加载顺序
加载器在JVM启动时的加载顺序是:
1,BootstrapClassLoader 引导类加载器
2,ExtentionClassLoader 引导类加载器
3,- 应用类加载器Application ClassLoader:应用类加载器,加载classpath下的字节码文件,用java编写,对应AppClassLoader这个类,可以通过ClassLoader类的静态方法getSystemClassLoader() 获得,所以又叫系统类加载器
那这里还有一个问题 为什么系统的classLoader 返回的是null?
很不幸,人家是 native 方法,看不到了,只能从注释中看了
Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
返回类的类装入器。某些实现可能使用
*null表示引导类加载器。此方法将返回
*如果该类是由引导加载的,则在此类实现中为null
*类加载器。
那么我们写的代码中是不是可以使用这种方式 判断是不是业务系统中调用的呢?试验一下
我靠 这个也不行啊,又是什么原因啊,怎么这么难。。。。
告诉我 需要 CallerSensitive 这个注解,行吧 ,我先不管 它是干什么的,你让我用这个注解,行,我自己搞一个看看
@CallerSensitive
public static Class<?> a(){
Class<?> caller = Reflection.getCallerClass();
return caller;
}
结果你给我返回
Exception in thread "main" java.lang.InternalError: CallerSensitive annotation expected at frame 1
at sun.reflect.Reflection.getCallerClass(Native Method)
at org.apache.rocketmq.client.test.B.a(B.java:46)
at org.apache.rocketmq.client.test.B.main(B.java:35)
不行了 找资料吧,openjdk.java.net/jeps/176
总结就是说 jdk内有些方法,jvm的开发者认为这些方法危险,不希望开发者调用,就把这种危险的方法用 @CallerSensitive修饰,并在“jvm”级别检查。
如Reflection.getCallerClass()方法规定,调用它的对象,必须有 @CallerSensitive 注解,否则 报异常 Exception in thread “main” java.lang.InternalError: CallerSensitive annotation expected at frame 1
@CallerSensitive 有个特殊之处,必须由 启动类classloader加载(如rt.jar ),才可以被识别。 所以rt.jar下面的注解可以正常使用。
开发者自己写的@CallerSensitive 不可以被识别。 但是,可以利用jvm参数 -Xbootclasspath/a: path 假装自己的程序是启动类。
到这,你就是告诉我这个方法调用不了被,是不是。。。。
什么,你还要问 @CallerSensitive 有个特殊之处,必须由 启动类classloader加载(如rt.jar ),才可以被识别 这个怎么证明,行吧,代码如下 在 sun.reflect.Reflection 类中
public static boolean isCallerSensitive(Method m) {
//获取 ClassLoader
final ClassLoader loader = m.getDeclaringClass().getClassLoader();
//必须是 BootstrapClassLoader 或者 ExtentionClassLoader 才能使用 @CallerSensitive 注解
if (sun.misc.VM.isSystemDomainLoader(loader) || isExtClassLoader(loader)) {
return m.isAnnotationPresent(CallerSensitive.class);
}
return false;
}
我知道 你可能还想问 -Xbootclasspath/a: path 为什么能 把自己当成 启动类的,淡定,先控制一下
继续往下,回到正题,如果我们自己的组件 需要让业务 不能用这个A类怎么做?
- 其实可以模仿@CallerSensitive 实现一个 我们自己的 自定义注解(这里叫C 注解),然后扫描自己 指定类中的 C 注解,不扫描 外部业务系统的注解,就可以让外部的不生效,报错
- 同时 实现自己公司的 idea 插件,扩展对 代码的检查,比如 如果在业务系统使用某一个不允许的类,进行提示,飘红,告知原因,模仿这种
redis lua命令 号称也是原子性的,我们还根据这个特性 去解决的redis 多命令的原子性问题呢。
应用场景:
1 之前做 文件服务中间件的时候 采用的 CS 架构,Server端协议用的 Raft协议,v1版本为了节省代码难度,使用的redis 保证分布式的版本控制
redis 主要存储2个值,一个是 版本号 和 对应的节点 worker的树结构,这就涉及到 2个值的数据一致性问题。 当时使用的lua 把修改这两个值的操作 通过lua 绑定到一起,解决的一致性问题
2 之前再写库存扣减的时候,为了保证高并发的性能,把库存 分为多块,用户买的时候 会根据写的lua脚本 扣减库存,可能分为了10块库存,第一块不够了,就看看能不能从别的块 扣减。
缓存行 分别加锁,是把锁细粒度化实现的优化,那其他地方有这个思想的其他实现么?
有,线程安全的Map List 都是 使用的锁细粒度 来优化并发问题的,分为16块,通过hash 把锁竞争的压力分发给16块。
java String 为什么是线程安全的
因为 一旦变化 就是一个新的String对象,旧的对象不变
最后
以上就是诚心鸭子为你收集整理的扒一扒@CallerSensitive注解,有点意思并发的全部内容,希望文章能够帮你解决扒一扒@CallerSensitive注解,有点意思并发所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复