CAS在Java类中的应用
CAS
这个指令全称 compare and swap 即比较替换指令,在现代处理器新加入的指令。
指导思想:基于乐观锁机制。比较一个变量在内存值中的值和变量的当前值(旧值)。如果相等,则认为该变量没有发生改变,使用新值替代旧值;否则认为替换失败。
Unsafe
在java程序的多线程环境中,如果一个变量被多个线程访问,要保证线程安全,除了 volatile、锁、final、static这些手段外,可以借助java提供的 sun.misc.Unsafe类
这个类两个特点:
1.平台相关的(java语言平台无关),所有方法为native类型,c语言写的。提供了很多native类型方法
更详细点的源码可以看-->OpenJDK中的Unsafe
2.我们可以以不安全的方式使用这个类,在下面的代码中getUnsafe()给出了推荐的使用方式。使用这个类时也可以借鉴一些JDK中自带的类,比如并发包中的原子类、Random、LockSupport.park()、ConcurrentHashMap等。
下面贴出我对Unsafe类源码的翻译分析
package sun.misc; import java.security.*;
import java.lang.reflect.*; import sun.reflect.CallerSensitive;
import sun.reflect.Reflection; /**
* 一个执行底层非安全的方法集合。虽然这个类及所有方法是公共的,
* 使用这个类是受限制的,只有被信任的代码才能获得这个类的实例。
* @author John R. Rose
* @查阅 方法getUnsafe()
*/ public final class Unsafe { private static native void registerNatives();
static {
registerNatives();
sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}
// 私有构造方法,用于单例
private Unsafe() {}
// 私有静态常量属性
private static final Unsafe theUnsafe = new Unsafe(); /**
* 这个类的多数方法是底层的,对应于一些硬件指令(在特定的机器)。
* 编译器会积极优化这个方法。
* 对于使用不安全的操作,一个比较推荐的语法:
* class MyTrustedClass {
* private static final Unsafe unsafe = Unsafe.getUnsafe();
* ...
* private long myCountAddress = ...;
* public int getCount() { return unsafe.getByte(myCountAddress); }
* }
*/
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
} /*
这个只作用在Java堆的对象属性上。
不作用在数组元素上。*/
public native int getInt(Object o, long offset); /**
* 将值存入Java变量。
* 前两个参数和getInt()方法一样,给定的值x被存入变量。
* 变量必须和方法的参数x同样的类型。
* @param o 参数o是java堆对象,即任何变量或null所寄存的地方。
* @param offset 表示变量寄存在Java堆的哪个位置,可以用一个内存地址定位变量位置。
注意:此处用long类型表示内存地址,正是因为long 8 个字节,支持64位处理器。
如果用int表示内存地址,那只能支持32处理器。
* @param x 被存入Java变量中的值
* @throws RuntimeException No defined exceptions are thrown, not even
* {@link NullPointerException}
*/
public native void putInt(Object o, long offset, int x); /**
* 这个方法,像所有其他32位偏移,在之前的发行版本1.4中是native方法,这里为了向后兼容1.4
* @deprecated As of 1.4.1, cast the 32-bit offset argument to a long.
* See {@link #staticFieldOffset}.
*/
@Deprecated
public int getInt(Object o, int offset) {
return getInt(o, (long)offset);
} /**
* @deprecated As of 1.4.1, cast the 32-bit offset argument to a long.
* See {@link #staticFieldOffset}.
*/
@Deprecated
public void putInt(Object o, int offset, int x) {
putInt(o, (long)offset, x);
} /**
* 从内存地址获取原生的指针,如果地址是0,或者没有指向一个内存地址块,结果是未知的。
* 如果原生指针小于64位宽度,它会被作为无符号数扩展到64位。
* 这个指针可以被给定的字节偏定位。从目标地址读到的字节数量可能由地址宽度决定。
* @see #allocateMemory
*/
public native long getAddress(long address); /**
* 存储一个原生指针到给定的内存地址。如果地址是0或者超过范围,结果将是未知的。
* 写入到目标地址的实际字节数量是用地址宽度决定的。
* @see #getAddress(long)
*/
public native void putAddress(long address, long x); /// wrappers for malloc分配内存, realloc扩大内存, free释放内存: /**
* 分配一个指定字节大小的原生内存块
* 内存内容是未初始化的;没有用的数据
* 结果原生指针将不会是0,将会指派一个值。
* 通过freeMemory方法处理掉(dispose)内容,或通过reallocateMemory方法重新调整大小
* @throws IllegalArgumentException 如果大小是负数或原生size_t太大,将会抛异常。
*
* @throws OutOfMemoryError if the allocation is refused by the system
* 如果系统拒绝分配内存,则抛内存溢出错误
* @see #getByte(long)
* @see #putByte(long, byte)
*/
public native long allocateMemory(long bytes); /**
* 将指定内存块的所有字节设置为固定值(通常为0)
* 这个方法通过两个参数决定一个块的基本地址,所以它提供了一个双寄存器寻址模式addressing mode,
* 正如getInt(Object,long)中所讨论的。当对象引用为null时,offset提供了(supply)一个绝对地址。
*
* 存储内容是连续的单元,大小由地址和参数长度决定的,如果地址有效且能被8取模,存储内容用long单元;
* 如果地址有效且能被4或2取模,就用int 或short取代。
* @since 1.7
*/
public native void setMemory(Object o, long offset, long bytes, byte value);
/**
* Sets all bytes in a given block of memory to a fixed value (usually zero).
* 将指定内存块的所有字节设置为固定值(通常为0)
* This provides a single-register addressing mode,as discussed in #getInt(Object,long).
* 这个方法提供了单寄存器寻址模式
* 相同于 Equivalent to setMemory(null, address, bytes, value).
*/
public void setMemory(long address, long bytes, byte value) {
setMemory(null, address, bytes, value);
} /**
* 复制内存块,这个方法提供了双寄存器寻址模式,当对象引用为null时,offset表示绝对地址。
* 这种复制在由大小确定的内存连续单元进行。
* @since 1.7
*/
public native void copyMemory(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes);
/**
* 复制内存块,这个方法提供了单寄存器寻址模式
* Equivalent to <code>copyMemory(null, srcAddress, null, destAddress, bytes)</code>.
*/
public void copyMemory(long srcAddress, long destAddress, long bytes) {
copyMemory(null, srcAddress, null, destAddress, bytes);
} /**
* 将从#allocateMemory,#reallocateMemory获得obtained的原生native内存块清除dispose掉。
* address为null时,不做任何处理。
* @see #allocateMemory
*/
public native void freeMemory(long address); /// random queries /**
* This constant differs from all results that will ever be returned from
* {@link #staticFieldOffset}, {@link #objectFieldOffset},
* or {@link #arrayBaseOffset}.
*/
public static final int INVALID_FIELD_OFFSET = -1; /**
* Returns the base address for accessing some static field in the given class.
* 为访问静态变量而返回指定类的基本地址
*/
@Deprecated
public Object staticFieldBase(Class<?> c) {
Field[] fields = c.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (Modifier.isStatic(fields[i].getModifiers())) {
return staticFieldBase(fields[i]);
}
}
return null;
} /**
* Report the location of a given static field, in conjunction with {@link #staticFieldBase}.
* 报告一个指定静态属性的位置,和#staticFieldBase配合执行。
* <p>Do not expect to perform any sort of arithmetic on this offset;
* 不要指望在这个偏移量上执行任何类型的运算。
* it is just a cookie which is passed to the unsafe heap memory accessors.
* 它只是一个cookie被传递到不安全的堆内存访问
* 任何给定字段总是具有相同的偏移量,同一类的两个不同字段不会有相同的偏移量。
*
* <p>As of 1.4.1, offsets for fields are represented as long values,
* 虽然SUN JVM不使用最有意义的32位
* However, JVM implementations which store static fields at absolute
* addresses can use long offsets and null base pointers to express the field locations in a form usable by {@link #getInt(Object,long)}.
* 然而,将静态属性存储在绝对地址的JVM版本可以使用长类型偏移和空的基本指针来表示变量位置
* Therefore, code which will be ported to such JVMs on 64-bit platforms must preserve all bits of static field offsets.
* 所以,代码将被移植到64位平台JVM在上必须保留所有位静态字段偏移量
* @see #getInt(Object, long)
*/
public native long staticFieldOffset(Field f); /**
* 报告一个指定静态属性的位置,和#staticFieldBase配合执行。
* 不要指望在这个偏移量上执行任何类型的运算。
* 它只是一个cookie被传递到不安全的堆内存访问
* 任何给定字段总是具有相同的偏移量,同一类的两个不同字段不会有相同的偏移量。
* JDK1.4.1,字段的偏移值是long类型的值
* 虽然SUN JVM不使用最有意义的32位
* 很难想象一个JVM技术需要超过几个位来对非数组对象中的偏移进行编码,
* 但是,为了与该类中的其他方法一致,该方法将其结果报告为一个长值。
* @see #getInt(Object, long)
*/
public native long objectFieldOffset(Field f); /**
* Report the size in bytes of a native pointer, as stored via {@link
* #putAddress}. This value will be either 4 or 8. Note that the sizes of
* other primitive types (as stored in native memory blocks) is determined
* fully by their information content.
*/
public native int addressSize(); /** The value of {@code addressSize()} */
public static final int ADDRESS_SIZE = theUnsafe.addressSize(); /**
* Report the size in bytes of a native memory page (whatever that is).
* This value will always be a power of two.
*/
public native int pageSize(); /// random trusted operations from JNI: /**
* Tell the VM to define a class, without security checks. By default, the
* class loader and protection domain come from the caller's class.
*/
public native Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain); /** Allocate an instance but do not run any constructor.
Initializes the class if it has not yet been. */
public native Object allocateInstance(Class<?> cls)
throws InstantiationException; /** 锁住对象,必须通过monitorExit解锁. */
public native void monitorEnter(Object o);
/**
* 解锁对象,必须已经通过#monitorEnter锁住
*/
public native void monitorExit(Object o); /**
* 尝试锁住对象,返回值true或false表示是否锁住。如果锁住,必须通过#monitorExit解锁。
*/
public native boolean tryMonitorEnter(Object o); /** Throw the exception without telling the verifier. */
public native void throwException(Throwable ee);
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* 如果对象是期待值,原子性更新Java变量为x
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x); /**
* Unblock the given thread blocked on <tt>park</tt>, or, if it is
* not blocked, cause the subsequent call to <tt>park</tt> not to
* block. Note: this operation is "unsafe" solely because the
* caller must somehow ensure that the thread has not been
* destroyed. Nothing special is usually required to ensure this
* when called from Java (in which there will ordinarily be a live
* reference to the thread) but this is not nearly-automatically
* so when calling from native code.
* @param thread the thread to unpark.
*
*/
public native void unpark(Object thread); /**
* Block current thread, returning when a balancing
* <tt>unpark</tt> occurs, or a balancing <tt>unpark</tt> has
* already occurred, or the thread is interrupted, or, if not
* absolute and time is not zero, the given time nanoseconds have
* elapsed, or if absolute, the given deadline in milliseconds
* since Epoch has passed, or spuriously (i.e., returning for no
* "reason"). Note: This operation is in the Unsafe class only
* because <tt>unpark</tt> is, so it would be strange to place it
* elsewhere.
*/
public native void park(boolean isAbsolute, long time); // The following contain CAS-based Java implementations used on
// platforms not supporting native instructions /**
* Atomically adds the given value to the current value of a field
* or array element within the given object <code>o</code>
* at the given <code>offset</code>.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param delta the value to add
* @return the previous value
* @since 1.8
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
} /**
* Atomically exchanges the given value with the current value of
* a field or array element within the given object <code>o</code>
* at the given <code>offset</code>.
*
* @param o object/array to update the field/element in
* @param offset field/element offset
* @param newValue new value
* @return the previous value
* @since 1.8
*/
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
/**
* Ensures lack of reordering of loads before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void loadFence();
public native void storeFence();
public native void fullFence(); }
AtomicLong
然后,那我们以AtomicLong为例,看JDK中是如何使用Unsafe的。
package java.util.concurrent.atomic;
import sun.misc.Unsafe; /**
* 一个AtomicLong被用作原子性增加的序列数字,不是Long的替代,
* 这个类的确继承了Number,允许使用工具类按接口获得实际数值。
* @since 1.5
* @author Doug Lea
*/
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L; // 这种声明方式,是sun官方推荐的、被信任的。我们在开发时就可以这样使用Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; /**
* 记录虚拟机 是否支持 对Long类型进行无锁的compareAndSwap。
* 无论虚拟机是否支持,一些构造器应该被控制在Java语言级别,避免显式锁的泄露。
*/
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); /**
* 记录底层虚拟机 是否支持 对Long类型进行无锁的CompareAndSet。
* 这个方法只会调用一次,结果缓存在VM_SUPPORTS_LONG_CAS。
*/
private static native boolean VMSupportsCS8(); static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
} private volatile long value; /**
* 用给定的初始值创建AtomicLong实例
* @param initialValue 初始值
*/
public AtomicLong(long initialValue) {
value = initialValue;
} public AtomicLong() {
} /**
* 返回当前值
* @return 当前值
*/
public final long get() {
return value;
} /**
* 设置给定值
*
* @param newValue 新值
*/
public final void set(long newValue) {
value = newValue;
} /**
* 最终设定值
*
* @param newValue the new value
* @since 1.6
*/
public final void lazySet(long newValue) {
unsafe.putOrderedLong(this, valueOffset, newValue);
} /**
* 原子性设定新值,返回旧值
* @param newValue 新值
* @return 旧值
*/
public final long getAndSet(long newValue) {
while (true) {
// 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
long current = get();
if (compareAndSet(current, newValue))
return current;
}
} /**
* 如果当前值==期待值,原子性地设置变量为给定更新值
* @param 期待值
* @param 要更新的新值
* @return 如果成功返回true.失败则返回false,表明实际值不等于期待值
*/
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
} /**
* 如果当前值==期待值,设定变量为给定更新值
* 可能假失败,不提供排序保证,所以,只是很少使用的选项。
* @param expect 期待值
* @param update 给定更新值
* @return 成功则返回true
*/
public final boolean weakCompareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
} /**
* 原子性递增当前值,返回当前值
* @return the previous value
*/
public final long getAndIncrement() {
while (true) {
// 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return current;
}
} /**
* 原子性递减当前值,返回当前值
*
* @return the previous value
*/
public final long getAndDecrement() {
while (true) {
// 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
long current = get();
long next = current - 1;
if (compareAndSet(current, next))
return current;
}
} /**
* 原子性地将当前值与给定值相加,当前值是未知的,即时获得的。
*
* @param delta 加数
* @return 先前值
*/
public final long getAndAdd(long delta) {
while (true) {
// 失败后,进行重试,get()返回值是未知的,即时获得的。并不是上次循环的旧值
long current = get();
long next = current + delta;
if (compareAndSet(current, next))
return current;
}
} /**
* 原子性地递增值
*
* @return 更新值
*/
public final long incrementAndGet() {
for (;;) {
long current = get();
long next = current + 1;
if (compareAndSet(current, next))
return next;
}
} }
CAS在Java类中的应用的更多相关文章
- Myeclipse中导入项目后java类中汉字注释出现乱码问题(已解决)
今天重装系统,安装了新的Myeclipse后,导入之前的项目后,,出现了乱码问题.乱码问题主要是java类中的注释,而jsp页面中汉字却完好如初: 右键项目,查看项目的编码格式,UTF-8,把java ...
- Java类中中文问题
一个奇怪问题 java类中要保存一个xml文件到数据库,2种传值方式其中1种不知何故会最终导致解析xml时报错. xml文件内容由StringBuffer定义,其中一段内容如下: sb.append( ...
- java类中定义接口
今天看到一个java类中定义了接口,写个备忘录,记录一下 package com.gxf.test; public class Test_interface { public interface sh ...
- thymeleaf模板引擎调用java类中的方法(附源码)
前言 <Docker+SpringBoot+Mybatis+thymeleaf的Java博客系统开源啦> 由于开源了项目的缘故,很多使用了My Blog项目的朋友遇到问题也都会联系我去解决 ...
- java类中根据已有的变量复写类的toString方法
java类中根据已有的变量复写类的toString方法: 在该类中定义好变量之后,shift+alt+s,从出现的列表中点击gemerate toString,就会自动生成对应的toString方法.
- java 类中的细节
java 中类: 类是用于描述统一类型的对象的一个抽象的概念,类中定义了这一类对象所因具有的静态和动态属性. 举例: 瓶子静态: 有一个口.长长的形状-->java类中的成员变量动态属性: 可以 ...
- Jsp中如何通过Jsp调用Java类中的方法
Jsp中如何通过Jsp调用Java类中的方法 1.新建一个项目,在src文件夹下添加一个包:如:cn.tianaoweb.com; 2.再在包中添加一个类:如 package com; public ...
- 第6章 Java类中的方法
1.如何定义java的方法 什么是方法:方法使用来解决一类问题的代码集合,是一个功能模块在类中定义个方法的方法是: 访问修饰符 返回值类型 方法名(参数列表){ 方法体 } 1.访问修饰符,是限制该方 ...
- Java 类中各成分加载顺序 和 内存中的存放位置
参加一个笔试,有一个关于类的静态代码块.构造代码块.构造函数的执行顺序的问题.不太清楚,网上百度了一下.在这里记录一下. 一.什么时候会加载类?使用到类中的内容时加载:有三种情况1.创建对象:new ...
随机推荐
- 用Vim 加密文本
Vim强大就在于 可以干任何想要做的事情,比如加密.Fedora 18上给大家做一个测试.首先安装 vim: sudo yum install vim -y然后检验模块是否有加密: vim --ver ...
- java 之 抽象工厂模式(大话设计模式)
看了几次抽象工厂模式,每次查看都需要重新理解一次,可能是涉及的类和接口比较多,所以比较难缕清的关系吧!在笔者看来,我们还是要吸取其思想而不是生搬硬套. 来看下类图: 大话设计模式-类图 看类图已经很乱 ...
- java 之 适配器模式(大话设计模式)
适配器模式,笔者不是很推荐在项目初期阶段使用,在笔者看来这个设计模式就是套接了一层,从而达到能够迎合现有的外部接口规范. 先来简单的看下类图: 大话设计模式-类图 这个模式理解起来非常简单,A→B因为 ...
- 大数据学习系列之一 ----- Hadoop环境搭建(单机)
一.环境选择 1,服务器选择 阿里云服务器:入门型(按量付费) 操作系统:linux CentOS 6.8 Cpu:1核 内存:1G 硬盘:40G ip:39.108.77.250 2,配置选择 JD ...
- 初窥c++11:lambda函数及其用法
转载于:点击打开链接 为什么需要lambda函数 匿名函数是许多编程语言都支持的概念,有函数体,没有函数名.1958年,lisp首先采用匿名函数,匿名函数最常用的是作为回调函数的值.正因为有这样的需求 ...
- 了解web及网络基础
了解web及网络基础 以下内容简单的说明了一下TCP/IP协议族中HTTP协议.DNS服务.IP协议的一些概念和关系.笔者只是对知识点进行了总结,仅供参考: ) 转载请注明出处:了解web及网络基础 ...
- lodash源码分析之compact中的遍历
小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...
- 【DevOps】团队敏捷开发系列--开山篇
随着软件发布迭代的频率越来越高,传统的「瀑布型」(开发-测试-发布)模式已经不能满足快速交付的需求.2009 年左右 DevOps 应运而生,开发运维一体化,通过自动化工具与流程让整个软件开发构建.测 ...
- ps的快捷键
最近学习了一些ps切图,总结一些快捷键,以免自己忘记,总结的不好,也可能不全,忘大牛指点,试着坚持总结 1.工具箱 (多种工具共用一个快捷键的可同时按[Shift]加此快捷键选取) 矩形.椭圆选框工具 ...
- 自己定义定时器(Timer)
近期做项目的时候,用到了java.util.Timer定时器类.也初步使用了,个人感觉不错.只是,在某些方面Timer类无法满足项目的需求.比方,在使用Timer时,调用schedule()方法之后( ...