原子类
jdk中的原子类是在java.util.concurrent.atomic
包下的,常用的就是AtomicInteger,AtomicBoolean,AtomicLong这几个,包下也包括数组、对象的原子类,这些原子类的实现都基本相似,以AtomicInteger为例,主要需要关注两个点:
-
数据存储的value是volatile修饰的。
private volatile int value;
这里先说一下volatile关键字,众所周知,volatile关键字有两个作用,修饰的变量对所有线程可见和禁止指令重排。对于volatile修饰的变量,只能保证对其他线程的可见性,但不能保证变量操作的原子性,例如线程A,B获取变量a,两个线程获取到的a一定是一样的,但如果对a进行加1操作,应该最终结果为3,但可能为2。
-
原子操作都是基于Unsafe类的CAS来实现的。
这里自己写了一个实验例子,让多个线程去对自增一个Integer,最后看这个数的最终值是否符合预期。实验目的有三点:
- 验证volatile关键字并不具备原子的特性
- 通过Unsafe的API来实现CAS
- 通过对Integer++自增操作加锁synchronized,来证明保证了操作的原子性
public class Main {
private volatile static Integer num1;
private volatile static Integer num2;
private static Integer num3;
private static final int c = 100;
public static void main(String[] args) throws Throwable {
ArrayList<Object> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
ArrayList<Object> list3 = new ArrayList<>();
for (int j = 0; j < 10; j++) {
num1 = 300;
num2 = 300;
num3 = 300;
//没有cas
CountDownLatch t1 = new CountDownLatch(c);
for (int i = c; i > 0; i--) {
new Thread(new Runnable() {
@Override
public void run() {
num1++;
t1.countDown();
}
}).start();
}
t1.await();
list1.add(num1);
//cas
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
Long valueOffset = unsafe.objectFieldOffset
(Integer.class.getDeclaredField("value"));
CountDownLatch t2 = new CountDownLatch(c);
for (int i = c; i > 0; i--) {
new Thread(new Runnable() {
@Override
public void run() {
int intVolatile;
do {
intVolatile = unsafe.getIntVolatile(num2, valueOffset);
} while (!unsafe.compareAndSwapInt(num2, valueOffset, intVolatile, intVolatile + 1));
//compareAndSwapInt(哪个对象,对象中的偏移量,初始值,期望值)
t2.countDown();
}
}).start();
}
t2.await();
list2.add(num2);
//synchronized
CountDownLatch t3 = new CountDownLatch(c);
for (int i = c; i > 0; i--) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (num3) {
num3++;
}
t3.countDown();
}
}).start();
}
t3.await();
list3.add(num2);
}
System.out.println(list1);
System.out.println(list2);
System.out.println(list3);
}
}
最后结果为:
[399, 400, 397, 400, 397, 400, 399, 397, 397, 399]
[400, 400, 400, 400, 400, 400, 400, 400, 400, 400]
[400, 400, 400, 400, 400, 400, 400, 400, 400, 400]
这里选用num的值为300是由于Integer类在装箱的时候存在integerCache缓存的原因,Integer装箱的时候会调用valueOf方法,valueOf的源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到这里会对输入参数i进行判断,如果i在IntegerCache的范围内(默认-128~127,可以通过vm启动参数修改),就不会创建新的Integer实例。这里如果选择num=0,对其进行num++操作,那么改变的永远是num的指针,也就是随着num++操作,num指向的地址在不断变化,不符合试验设计需要,并且num=0初始化赋值的操作将没有意义。下图中可以看出来,变化的只是num的指针地址。
Unsafe类
Unsafe类是在sun.misc包下的,并不是java推荐的使用API,因为正如同名字一样,使用这个类很有可能带来问题,但这个类确在Netty等一些框架中广泛使用,这是因为Unsafe类提供了直接操作底层计算机的方法,可以极大提升性能
1. 构造方法
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
unsafe类是典型的单例模式建设的类,但并不能通过静态方法getUnsafe来获取到unsafe的实例,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法,可以通过反射的方式去获取,例如实验例子中获取Unsafe实例的方式。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
2. Unsafe类的主要使用场景
再说一下Unsafe类,这个类提供了一些直接操作内存,线程等的方法,但在日常开发中不会使用到Unsafe类,也不推荐使用,但在一些底层的操作,例如Netty、kafka等都使用了Unsafe类,这里对这个类的使用场景(能做的事情)进行一下简单的介绍。
- 直接操作内存(基本都是native方法)
该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。
利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。 - 不用new或反射也能去创建对象
通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。 - 操作类、对象、变量
这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。
通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。 - 多线程同步(CAS和锁机制)
锁机制:monitorEnter、tryMonitorEnter、monitorExit,但已被废弃。
CAS:compareAndSwapInt、compareAndSwap等,是Unsafe类用的最多的方法,原子类的实现原理。 - 线程的挂起(park)和恢复(unpark)
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。