由于国内的互联网企业基本不用 C#,所以想要去得赶快研究下 Java。虽然 Java 和 C# 在很多地方都差不多,不过还是有不少区别。这个系列就是专门用来学习 JDK 的源码的。
Java 的设计不如 C#的好,从这篇文章你就能看出。很多东西如果不知道背后的具体实现会觉得很莫名其妙。
考虑如下代码:
Integer i1 = 18;
Integer i2 = 18;
Long l1 = 18l;
Long l2 = 18l;
System.out.println(i1 == i2);
System.out.println(l1 == l2);
System.out.println(l1.equals(i1));
在 Java 中,Integer 不是基元类型 int 的别名,所以这里的i1、i2、l1、l2都是在栈上分配的对象(和 .NET 有很大区别)。而 Java 不支持运算符重载,因此对象间的 == 运算符肯定是检查的实例对象是否是同一个,而 equals 方法则基本都会被覆盖。
执行一下程序,发现输出为true/true/false。那么把程序改一下:
Integer i1 = 200;
Integer i2 = 200;
Long l1 = 200l;
Long l2 = 200l;
System.out.println(i1 == i2);
System.out.println(l1 == l2);
System.out.println(l1.equals(i1));
此时输出 false/false/false。
看上去好像比较小的整数会有缓存。我们再改写程序:
Integer i1 = new Integer(18);
Integer i2 = new Integer(18);
Long l1 = new Long(18);
Long l2 = new Long(18);
System.out.println(i1 == i2);
System.out.println(l1 == l2);
程序输出 false/false。
Java 的这个行为令人觉得非常怪异,那么只能查看源代码。在 Integer.java 中,我们发现了一个内部类:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low));
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
显然,Java 把-128~127间的整数对应的实例对象进行了缓存。在 Long 中也有类似的 LongCache,不过 LongCache 的长度是不可配置的。那么这个 Cache 在什么时候用呢?查看源代码,发现只有 valueOf 方法才会使用:
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
那么原因就清楚了:只有调用 valueOf 或者进行自动装箱的时候这个 Cache 才会派上用场,直接使用构造方法生成的对象则不会是 Cache 中的对象,所以此时会返回 false。
由于 Java 的这个不一致的实现,所以无论如何都不可以用 == 号进行装箱后的数字的判断。那么 equals 方法呢?
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
只有另一个对象也是 Integer ,并且它们拆箱后的值相等的时候才返回 true。也就是说你把一个数字量相等的 Long 与它进行 equals 判断仍然是不相等。所以,也不要使用 equals 来进行判断。用 == 符号判断 Integer 和 Long 更不可行,因为类型不一致编译器直接报错。
因此如果进行数字量判断,唯一的办法是使用拆箱后的值进行比较,否则别说是你,Oracle 自己估计都弄不清会发生什么!
Integer i1 = new Integer(18);
Long l1 = new Long(18);
long l3 = l1;
long l4 = i1;
System.out.println(l3 == l4); //true
所以说 Java 在基础类型这一块设计得很烂。.NET 就不会有这个问题,因为基础类型对应的类都是 int 之类的别名,在 CLR 中都是当作值类型进行处理的。
有一个家公司的笔试题和这些基元类型对应的对象的 == 和 equals 方法有关。个人觉得很不合适,因为采用这种做法是不提倡的,完全没必要用这个去考察一个面试者,除非是 Oracle 在招聘。
接下来看一些比较有意思的实现。
取符号位:
public static int signum(int i) {
// HD, Section 2-7
return (i >> 31) | (-i >>> 31);
}
按位反续:
public static int reverse(int i) {
// HD, Figure 7-1
i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
i = (i << 24) | ((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) | (i >>> 24);
return i;
}
大家可以自行想想原理,很考验智商。
AtomicInteger (以及其他类似的类)是提供整数相加、相减操作的线程安全的类。私有字段必然是:
private volatile int value;
直接设置、获取值本身是线程安全的。但是相加(包括++)和相减的操作肯定不是线程安全的了。
public final int getAndAdd(int delta) {
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
compareAndSet 会调用一个名为 compareAndSwapInt 的 native 的方法。代码如下:
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);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
inline jint Atomic::cmpxchg(jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
jint 是在 native 的 C++ 语言中对应的 Java 的 int 类型,可以参考任意一本关于JNI的书。
cmpxchg 是机器指令支持的原子操作,和 .NET 里面的 Interlocked.CompareExchage 一样,在 Windows 上是调用的 WINAPI。
也就是说会用目前真实的值与 current 进行比较,如果相等,则其他线程没有进行修改,可以安全地把结果 next 写入。否则,当前值不是最新的,再取一遍当前值进行运算,直到成功为止。在 cmpxchg 函数中,返回值是待修改区域的当前值(在进行替换之前),用它和程序保存的 current 进行比较,就可以知道是否被修改过了。
与 .NET 的 ConcurrentQueue 之类的实现不同,Java 没有使用自旋锁而是直接马上进行下一次尝试。目测是 Java 认为操作很快,以至于不需要考虑 SpinWait 吧。
还有一个叫做 lazySet 的方法。上面的这种原子性的操作虽然使用了机器指令保证原子性,但是相对于直接读写代价还是比较高的。如果我们允许其他读线程可以不读最新的值,那么可以考虑使用 lazySet 提高性能。
UNSAFE_ENTRY(void, Unsafe_SetOrderedInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint x))
UnsafeWrapper("Unsafe_SetOrderedInt");
SET_FIELD_VOLATILE(obj, offset, jint, x);
UNSAFE_END
这里使用了 xchgq 指令,貌似没有找到相关的说明。如果你们找到了请告诉我。
还有一个 BigInteger 的实现,这个貌似比较复杂,以后再分析吧。