Unsafe类提供了一些比较底层的不安全的操作. 因此此类只有那些可以被信任的代码才能调用. 此类只能通过其静态方法getUnsafe()
获得其实例. Unsafe类的这些底层操作非常有用, 因此非常多的库或应用都使用了Unsafe的相关功能
一、 获取UnSafe实例方法
private static final Unsafe unsafe = Unsafe.getUnsafe();
// Unsafe部分源码,通过以下可以看出该类是一个单例类
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
二、allocateInstance(Class<?> var1)方法
- 不通过构造方法就实例化出一个对象.
-
此功能的运用
一些常见的JSON库一般都需要一个无参构造方法来实例化一个POJO对象, 有了Unsafe
类的这个功能, 就算没有构造方法, 这些JSON库也可以实例化对象了. Gson库就使用了该功能,
详情见: com.google.gson.internal.UnsafeAllocator. 有一个专门用来创建对象 (绕开构造方法) 的库Objenesis底层也有此功能的运用.
GJSON应用
com.google.gson.internal.UnsafeAllocator#create
package com.google.gson.internal;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public abstract class UnsafeAllocator {
public UnsafeAllocator() {
}
public abstract <T> T newInstance(Class<T> var1) throws Exception;
public static UnsafeAllocator create() {
try {
// 获取Unsafe的Class实例
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
// 获取theUnsafe字段
Field f = unsafeClass.getDeclaredField("theUnsafe");
// 设置可访问标识为true,可修改私有字段
f.setAccessible(true);
final Object unsafe = f.get((Object)null);
// 获取Unsafe类的分配实例方法 allocateInstance
final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
// UnsafeAllocator 类实现
return new UnsafeAllocator() {
// 实现newInstance方法,
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
// 通过allocateInstance 方法反射构造对象。
return allocateInstance.invoke(unsafe, c);
}
};
} catch (Exception var6) {
final Method newInstance;
try {
newInstance = ObjectStreamClass.class.getDeclaredMethod("getConstructorId", Class.class);
newInstance.setAccessible(true);
final int constructorId = (Integer)newInstance.invoke((Object)null, Object.class);
final Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance", Class.class, Integer.TYPE);
newInstance.setAccessible(true);
return new UnsafeAllocator() {
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
return newInstance.invoke((Object)null, c, constructorId);
}
};
} catch (Exception var5) {
try {
newInstance = ObjectInputStream.class.getDeclaredMethod("newInstance", Class.class, Class.class);
newInstance.setAccessible(true);
return new UnsafeAllocator() {
public <T> T newInstance(Class<T> c) throws Exception {
assertInstantiable(c);
return newInstance.invoke((Object)null, c, Object.class);
}
};
} catch (Exception var4) {
return new UnsafeAllocator() {
public <T> T newInstance(Class<T> c) {
throw new UnsupportedOperationException("Cannot allocate " + c);
}
};
}
}
}
}
static void assertInstantiable(Class<?> c) {
int modifiers = c.getModifiers();
if (Modifier.isInterface(modifiers)) {
throw new UnsupportedOperationException("Interface can't be instantiated! Interface name: " + c.getName());
} else if (Modifier.isAbstract(modifiers)) {
throw new UnsupportedOperationException("Abstract class can't be instantiated! Class name: " + c.getName());
}
}
}
三、compareAndSwapInt
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
说明:该方法为Java的native方法,底层由c实现。此处我们只关心该方法的作用即可。
方法的作用是,读取传入对象o在内存中偏移量为offset位置的值与期望值expected作比较。
相等就把x值赋值给offset位置的值。方法返回true。
不相等,就取消赋值,方法返回false。
即CAS交换思想,相等则交换,此方法底层保证在并发情况下只能有一个线程获取成功。
1、java.util.concurrent.atomic.AtomicBoolean#compareAndSet 底层实现
在openjdk中查找unsafe.cpp文件,可看到如下函数定义
// 在unsafe.cpp中查找compareAndSwapInt,可以看到以下内容(大概是java到c的一个映射,具体没查过哈)
{CC"compareAndSwapInt", CC"("OBJ"J""I""I"")Z", FN_PTR(Unsafe_CompareAndSwapInt)},
// 然后看Unsafe_CompareAndSwapInt方法
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
// 最终保证原子性是通过,硬件层面的指令,comxchg完成的。
// 如果是多核的情况下,就会上锁。LOCK_IF_MP(mutil Processors)
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
// JNIEnv*表示的是JNI本身,通过它可以调用jni.h中定义好的函数
JNIEnv *env,
// Unsafe类
jobject unsafe,
// jobject表示当前对象(实例对象或者class)
jobject obj,
// 偏移量
jlong offset,
// 期待值(内存旧值)
jint e,
// 待更新值
jint x
》可通过JNI进行自定义native函数,并进行调用,此处不扩展。
2、分析:
最终保证原子性是通过,硬件层面的指令,comxchg完成的。如果是多核的情况下,就会上锁。LOCK_IF_MP(mutil Processors),分析如下
汇编指令 | 作用 |
cmpxchg
| 比较并交换操作数. |
》atomic.cpp文件
cmpxchg命令在atomic.cpp文件中,部分内容如下
#include "precompiled.hpp"
#include "runtime/atomic.hpp"
#ifdef TARGET_OS_FAMILY_linux
# include "os_linux.inline.hpp"
#endif
#ifdef TARGET_OS_FAMILY_solaris
# include "os_solaris.inline.hpp"
#endif
#ifdef TARGET_OS_FAMILY_windows
# include "os_windows.inline.hpp"
#endif
#ifdef TARGET_OS_FAMILY_aix
# include "os_aix.inline.hpp"
#endif
#ifdef TARGET_OS_FAMILY_bsd
# include "os_bsd.inline.hpp"
#endif
// 【引入头文件】
#include "runtime/atomic.inline.hpp"
jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
assert(sizeof(jbyte) == 1, "assumption.");
uintptr_t dest_addr = (uintptr_t)dest;
uintptr_t offset = dest_addr % sizeof(jint);
volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
jint cur = *dest_int;
jbyte* cur_as_bytes = (jbyte*)(&cur);
jint new_val = cur;
jbyte* new_val_as_bytes = (jbyte*)(&new_val);
new_val_as_bytes[offset] = exchange_value;
while (cur_as_bytes[offset] == compare_value) {
jint res = cmpxchg(new_val, dest_int, cur);
if (res == cur) break;
cur = res;
new_val = cur;
new_val_as_bytes[offset] = exchange_value;
}
return cur_as_bytes[offset];
}
》atomic.inline.hpp
里面我们以atomic_linux_x86.inline.hpp为例
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
#define SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
#include "runtime/atomic.hpp"
// Linux
#ifdef TARGET_OS_ARCH_linux_x86
# include "atomic_linux_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "atomic_linux_sparc.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "atomic_linux_zero.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "atomic_linux_arm.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "atomic_linux_ppc.inline.hpp"
#endif
// Solaris
#ifdef TARGET_OS_ARCH_solaris_x86
# include "atomic_solaris_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "atomic_solaris_sparc.inline.hpp"
#endif
// Windows
#ifdef TARGET_OS_ARCH_windows_x86
# include "atomic_windows_x86.inline.hpp"
#endif
// AIX
#ifdef TARGET_OS_ARCH_aix_ppc
# include "atomic_aix_ppc.inline.hpp"
#endif
// BSD
#ifdef TARGET_OS_ARCH_bsd_x86
# include "atomic_bsd_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "atomic_bsd_zero.inline.hpp"
#endif
#endif // SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP
》atomic_linux_x86.inline.hpp
// 在MP(多核cpu)机器上给指令添加一个锁前缀
// Adding a lock prefix to an instruction on MP machine
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
=====================================================================================
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// 判断是否为多处理器,multiprocessor system
int mp = os::is_MP();
// volatile 防止内存指令优化,如果是多核处理器,则添加锁前缀(注意LOCK_IF_MP方法)
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
=====================================================================================
是否为多核处理器判断
static inline bool is_MP() {
// During bootstrap if _processor_count is not yet initialized
// we claim to be MP as that is safest. If any platform has a
// stub generator that might be triggered in this phase and for
// which being declared MP when in fact not, is a problem - then
// the bootstrap routine for the stub generator needs to check
// the processor count directly and leave the bootstrap routine
// in place until called after initialization has ocurred.
// _processor_count处理器个数,至少一个
return (_processor_count != 1) || AssumeMP;
}
LOCK_IF_MP(%4) 从外观来看像是加锁的操作,我们进到LOCK_IF_MP这个方法内。
#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
注意【lock;1】含义: 如果只有一个cpu一核的话就不需要原子性了,一定是顺序执行的,如果是多核心多cpu前面就要加lock;
所以最红能够实现CAS的汇编指令就被我们揪出来了。最终的汇编指令是lock cmpxchg 指令,lock指令在执行后面指令的时候锁定一个北桥信号(锁定北桥信号比锁定总线轻量一些,感兴趣的自己百度)。
所以如果你是多核或者多个cpu,CPU在执行cmpxchg指令之前会执行lock锁定总线,实际是锁定北桥信号。我不释放这把锁谁也过不去,以此来保证cmpxchg的原子性。
总结:
1、CAS并不是真的无锁
2、记住指令lock cmpxch指令,明白实现原理,面试装逼...
GCC扩展:
GCC Inline ASM
GCC 支持在C/C++代码中嵌入汇编代码,这些汇编代码被称作GCC Inline ASM——GCC内联汇编。这是一个非常有用的功能,有利于我们将一些C/C++语法无法表达的指令直接潜入C/C++代码中,另外也允许我们直接写 C/C++代码中使用汇编编写简洁高效的代码
2.基本内联汇编的格式
如: __asm__ __volatile__("Instruction List");
1、__asm__
__asm__是GCC关键字asm的宏定义:
__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。
Instruction List是汇编指令序列。它可以是空的,
比如:__asm__ __volatile__(""); 或__asm__ ("");都是完全合法的内联汇编表达式,
只不过这两条语句没有什么意义。但并非所有Instruction List为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory"); 就非常有意义,它向GCC声明:“我对内存作了改动”,GCC在编译的时候,会将此因素考虑进去。
2、__volatile__
__volatile__是GCC关键字volatile的宏定义:
__volatile__ 或volatile是可选的,你可以用它也可以不用它。
如果你用了它,则是向GCC声明“不要动我所写的Instruction List,我需要原封不动的保留每一条指令”,
否则当你使用了优化选项(-O)进行编译时,
GCC将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。
参考链接:
https://www.jianshu.com/p/ce616620e720
https://blog.youkuaiyun.com/huangzhilin2015/article/details/101158137
https://cloud.tencent.com/developer/article/1434865
https://zhuanlan.zhihu.com/p/126384164