[多线程系列]Java 中atomic包的解析
前言:
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便在多线程环境下进行原子操作。原子变量的底层使用了CPU提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。
包介绍:
在atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。
原子更新基本类型
- AtomicInteger
- AtomicLong
- AtomicBoolean
原子更新引用
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
原子更新数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
原子更新字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicReferenceFieldUpdater
以下主要分析AtomicInteger,其他类大同小异
源码:
1 /* 2 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20 * 21 * 22 * 23 */ 24 25 /* 26 * 27 * 28 * 29 * 30 * 31 * Written by Doug Lea with assistance from members of JCP JSR-166 32 * Expert Group and released to the public domain, as explained at 33 * http://creativecommons.org/publicdomain/zero/1.0/ 34 */ 35 36 package java.util.concurrent.atomic; 37 import sun.misc.Unsafe; 38 39 /** 40 * An {@code int} value that may be updated atomically. See the 41 * {@link java.util.concurrent.atomic} package specification for 42 * description of the properties of atomic variables. An 43 * {@code AtomicInteger} is used in applications such as atomically 44 * incremented counters, and cannot be used as a replacement for an 45 * {@link java.lang.Integer}. However, this class does extend 46 * {@code Number} to allow uniform access by tools and utilities that 47 * deal with numerically-based classes. 48 * 49 * @since 1.5 50 * @author Doug Lea 51 */ 52 public class AtomicInteger extends Number implements java.io.Serializable { 53 private static final long serialVersionUID = 6214790243416807050L; 54 55 // setup to use Unsafe.compareAndSwapInt for updates 56 private static final Unsafe unsafe = Unsafe.getUnsafe(); 57 private static final long valueOffset; 58 59 static { 60 try { 61 valueOffset = unsafe.objectFieldOffset 62 (AtomicInteger.class.getDeclaredField("value")); 63 } catch (Exception ex) { throw new Error(ex); } 64 } 65 66 private volatile int value; 67 68 /** 69 * Creates a new AtomicInteger with the given initial value. 70 * 71 * @param initialValue the initial value 72 */ 73 public AtomicInteger(int initialValue) { 74 value = initialValue; 75 } 76 77 /** 78 * Creates a new AtomicInteger with initial value {@code 0}. 79 */ 80 public AtomicInteger() { 81 } 82 83 /** 84 * Gets the current value. 85 * 86 * @return the current value 87 */ 88 public final int get() { 89 return value; 90 } 91 92 /** 93 * Sets to the given value. 94 * 95 * @param newValue the new value 96 */ 97 public final void set(int newValue) { 98 value = newValue; 99 } 100 101 /** 102 * Eventually sets to the given value. 103 * 104 * @param newValue the new value 105 * @since 1.6 106 */ 107 public final void lazySet(int newValue) { 108 unsafe.putOrderedInt(this, valueOffset, newValue); 109 } 110 111 /** 112 * Atomically sets to the given value and returns the old value. 113 * 114 * @param newValue the new value 115 * @return the previous value 116 */ 117 public final int getAndSet(int newValue) { 118 for (;;) { 119 int current = get(); 120 if (compareAndSet(current, newValue)) 121 return current; 122 } 123 } 124 125 /** 126 * Atomically sets the value to the given updated value 127 * if the current value {@code ==} the expected value. 128 * 129 * @param expect the expected value 130 * @param update the new value 131 * @return true if successful. False return indicates that 132 * the actual value was not equal to the expected value. 133 */ 134 public final boolean compareAndSet(int expect, int update) { 135 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 136 } 137 138 /** 139 * Atomically sets the value to the given updated value 140 * if the current value {@code ==} the expected value. 141 * 142 * <p>May <a href="package-summary.html#Spurious">fail spuriously</a> 143 * and does not provide ordering guarantees, so is only rarely an 144 * appropriate alternative to {@code compareAndSet}. 145 * 146 * @param expect the expected value 147 * @param update the new value 148 * @return true if successful. 149 */ 150 public final boolean weakCompareAndSet(int expect, int update) { 151 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 152 } 153 154 /** 155 * Atomically increments by one the current value. 156 * 157 * @return the previous value 158 */ 159 public final int getAndIncrement() { 160 for (;;) { 161 int current = get(); 162 int next = current + 1; 163 if (compareAndSet(current, next)) 164 return current; 165 } 166 } 167 168 /** 169 * Atomically decrements by one the current value. 170 * 171 * @return the previous value 172 */ 173 public final int getAndDecrement() { 174 for (;;) { 175 int current = get(); 176 int next = current - 1; 177 if (compareAndSet(current, next)) 178 return current; 179 } 180 } 181 182 /** 183 * Atomically adds the given value to the current value. 184 * 185 * @param delta the value to add 186 * @return the previous value 187 */ 188 public final int getAndAdd(int delta) { 189 for (;;) { 190 int current = get(); 191 int next = current + delta; 192 if (compareAndSet(current, next)) 193 return current; 194 } 195 } 196 197 /** 198 * Atomically increments by one the current value. 199 * 200 * @return the updated value 201 */ 202 public final int incrementAndGet() { 203 for (;;) { 204 int current = get(); 205 int next = current + 1; 206 if (compareAndSet(current, next)) 207 return next; 208 } 209 } 210 211 /** 212 * Atomically decrements by one the current value. 213 * 214 * @return the updated value 215 */ 216 public final int decrementAndGet() { 217 for (;;) { 218 int current = get(); 219 int next = current - 1; 220 if (compareAndSet(current, next)) 221 return next; 222 } 223 } 224 225 /** 226 * Atomically adds the given value to the current value. 227 * 228 * @param delta the value to add 229 * @return the updated value 230 */ 231 public final int addAndGet(int delta) { 232 for (;;) { 233 int current = get(); 234 int next = current + delta; 235 if (compareAndSet(current, next)) 236 return next; 237 } 238 } 239 240 /** 241 * Returns the String representation of the current value. 242 * @return the String representation of the current value. 243 */ 244 public String toString() { 245 return Integer.toString(get()); 246 } 247 248 249 public int intValue() { 250 return get(); 251 } 252 253 public long longValue() { 254 return (long)get(); 255 } 256 257 public float floatValue() { 258 return (float)get(); 259 } 260 261 public double doubleValue() { 262 return (double)get(); 263 } 264 265 }
成员变量
1 private volatile int value;//值 2 private static final Unsafe unsafe = Unsafe.getUnsafe();//底层实现类unsafe 3 private static final long valueOffset;//值相对于对象的偏移量
静态代码块(关于unsafe的objectFieldOffset这个方法可以参见另外一篇文章,链接
static { try { //偏移量 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
构造函数
/** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } /** * Creates a new AtomicInteger with initial value {@code 0}. */ public AtomicInteger() { }
get/set方法
/** * Gets the current value. * * @return the current value */ public final int get() { return value; } /** * Sets to the given value. * * @param newValue the new value */ public final void set(int newValue) { value = newValue; }
getAndSet
/** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final int getAndSet(int newValue) { for (;;) { int current = get(); if (compareAndSet(current, newValue)) return current; } }
先调用get方法获取当前的值,然后无限循环调用compareAndSet方法来将当前的值更新为新的值,看下compareAndSet的源码
compareAndSet
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
可以看出调用了unsafe.compareAndSwapInt来更新值,compareAndSwapInt这个方法就是在CAS文章中提到的jdk中针对CAS,cpu指令提供的语言的实现,这个最后还是在cpu层面实现.
继续看其他方法
getAndIncrement
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
先获取当前的值,然后把当前的值增加1,然后调用compareAndSet方法将值更新为新值,一直到成功为止,返回之前的旧值
incrementAndGet
/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
类似getAndIncrement 但是最后返回的不是旧值而是新值,其实这个很好判断,看方法名,get在前就说明返回get的值,increment在前就说明返回increment操作以后的值
getAndAdd
/** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */ public final int getAndAdd(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return current; } }
大同小异,只是把+1变成了加上指定的值,返回之前的值
lazzySet
/** * Eventually sets to the given value. * * @param newValue the new value * @since 1.6 */ public final void lazySet(int newValue) { unsafe.putOrderedInt(this, valueOffset, newValue); }
先拿来跟set方法对比下发现lazySet的注释写的是 最终会设置成给的值(也许不保证可见性?),我们继续点到unsafe的putOrderedInt方法中可以看到
public native void putOrderedInt(Object var1, long var2, int var4);
这个方法调用了本地方法,看不到具体实现,所以只能找找别的资料,于是我在so上搜到了这样一个解释
As probably the last little JSR166 follow-up for Mustang, we added a "lazySet" method to the Atomic classes (AtomicInteger, AtomicReference, etc). This is a niche method that is sometimes useful when fine-tuning code using non-blocking data structures. The semantics are that the write is guaranteed not to be re-ordered with any previous write, but may be reordered with subsequent operations (or equivalently, might not be visible to other threads) until some other volatile write or synchronizing action occurs).
The main use case is for nulling out fields of nodes in non-blocking data structures solely for the sake of avoiding long-term garbage retention; it applies when it is harmless if other threads see non-null values for a while, but you'd like to ensure that structures are eventually GCable. In such cases, you can get better performance by avoiding the costs of the null volatile-write. There are a few other use cases along these lines for non-reference-based atomics as well, so the method is supported across all of the AtomicX classes.
For people who like to think of these operations in terms of machine-level barriers on common multiprocessors, lazySet provides a preceeding store-store barrier (which is either a no-op or very cheap on current platforms), but no store-load barrier (which is usually the expensive part of a volatile-write).
简单来说就是lazyset这个方法是非volatile的,使用这个方法更改状态是不能保证这次更改对其他线程可见的,但是提高了更高的写入性能,所以使用见仁见智.
weekCompareAndSet
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p>May <a href="package-summary.html#Spurious">fail spuriously</a> * and does not provide ordering guarantees, so is only rarely an * appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return true if successful. */ public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
这个方法很奇怪,奇怪的地方在于他的实现和compareAndSet是一毛一样的,但是上面的注释却说这个方法可能失败!!!令我十分不解,然后我去jdk文档的atomic包说明中找到了相关描述
- 以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。所以事情就指向了happen-before是什么鬼
继续搜 一直找到这篇文章 http://www.ibm.com/developerworks/library/j-jtp09238/index.html
Visibility failures
More subtle than atomicity failures are visibility failures. In the absence of synchronization, if one thread writes to a variable and another thread reads that same variable, the reading thread could see stale, or out-of-date, data. Worse, it is possible for the reading thread to see up-to-date data for variable
x
and stale data for variabley
, even ify
was written beforex
. Visibility failures are subtle because they don't happen predictably, or even frequently, causing rare and difficult-to-debug intermittent failures. Visibility failures are created by data races — failure to properly synchronize when accessing shared variables. Programs with data races are, for all intents and purposes, broken, in that their behavior cannot be reliably predicted.The Java Memory Model (JMM) defines the conditions under which a thread reading a variable is guaranteed to see the results of a write in another thread. (A full explanation of the JMM is beyond the scope of this article; see Resources.) The JMM defines an ordering on the operations of a program called happens-before. Happens-before orderings across threads are only created by synchronizing on a common lock or accessing a common volatile variable. In the absence of a happens-before ordering, the Java platform has great latitude to delay or change the order in which writes in one thread become visible to reads of that same variable in another.
The code in Listing 2 has visibility failures as well as atomicity failures. The
updateHighScore()
method retrieves theHighScore
object from theServletContext
and then modifies the state of theHighScore
object. The intent is for those modifications to be visible to other threads that callgetHighScore()
, but in the absence of a happens-before ordering between the writes to the name and score properties inupdateHighScore()
and the reads of those properties in other threads callinggetHighScore()
, we are relying on good luck for the reading threads to see the correct values.
英语不太好基本快阵亡了,不过大致还是看懂了解释,意思就是 一个线程写一个变量的值的时候要保证其他线程在读取这个值的时候是能看到写操作的(这不就是可见性么),JMM就把这一系列的操作称为happen-before,weekCompareAndSet和compareAndSet
区别在于week不会创建happen-before序列,所以也就是不提供可见性,所以有可能失败.(不过实现一毛一样是为什么!!),这个解释纯粹是自己理解,有高人可以留言解释下,实在是不太懂.
其他方法就不分析,基本都是类似的.关于unsafe不太懂的可以去看看关于CAS的那篇文章.
至于其他原子类AtomicLong等其他基本都是unsafe的包装类,不过有点需要注意,unsafe关于cas只提供了三种
所以很多其他的类似 AtomicBoolean 中的compareAndSet方法都是转换成unsafe.compareAndSwapInt来调用
/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); }
Atomic包 大致如此...核心类还是unsafe和cas思想.
最后附上该包下面所有类的测试代码!
1 package com.iwjw.learn.thread; 2 3 import java.util.concurrent.atomic.*; 4 5 /** 6 * 原子类的包测试 7 */ 8 public class AtomicTest { 9 static class Student { 10 private Long id; 11 12 public Student() { 13 } 14 15 public Student(Long id) { 16 this.id = id; 17 } 18 19 public Long getId() { 20 return id; 21 } 22 23 public void setId(Long id) { 24 this.id = id; 25 } 26 } 27 28 static class Person { 29 30 private int age; 31 private String name; 32 public volatile Student student = new Student(10086l); 33 34 public volatile int length; 35 public volatile long tall; 36 37 public Person(int length) { 38 this.length = length; 39 } 40 41 public Person(long tall) { 42 this.tall = tall; 43 } 44 45 public Person(int age, String name) { 46 this.age = age; 47 this.name = name; 48 } 49 50 public Person() { 51 } 52 53 public long getTall() { 54 return tall; 55 } 56 57 public void setTall(long tall) { 58 this.tall = tall; 59 } 60 61 public int getLength() { 62 return length; 63 } 64 65 public void setLength(int length) { 66 this.length = length; 67 } 68 69 public int getAge() { 70 return age; 71 } 72 73 public void setAge(int age) { 74 this.age = age; 75 } 76 77 public String getName() { 78 return name; 79 } 80 81 public void setName(String name) { 82 this.name = name; 83 } 84 85 public Student getStudent() { 86 return student; 87 } 88 89 public void setStudent(Student student) { 90 this.student = student; 91 } 92 } 93 94 95 public static void main(String[] args) { 96 // atomicIntegerTest(); 97 // atomicLongTest(); 98 // atomicBooleanTest(); 99 // atomicRefTest(); 100 // atomicIntegerArrayTest(); 101 // atomicLongArrayTest(); 102 // atomicRefArrayTest(); 103 // atomicIntegerFieldUpdaterTest(); 104 // atomicLongFieldUpdaterTest(); 105 // atomicRefFieldUpdterTest(); 106 // atomicMarkableRefTest(); 107 // atomicStampedRefTest(); 108 } 109 110 111 /** 112 * 测试 atomicInteger 113 */ 114 private static void atomicIntegerTest() { 115 AtomicInteger integer = new AtomicInteger(1); 116 System.out.println("get:" + integer.get());//1 117 System.out.println("incrementAndGet:" + integer.incrementAndGet());//2 118 System.out.println("getAndIncrement:" + integer.getAndIncrement());//2 119 System.out.println("getAndSet(10):" + integer.getAndSet(10));//3 120 System.out.println("compareAndSet(10,15):" + integer.compareAndSet(10, 15));//true 121 System.out.println("compareAndSet(14,20):" + integer.compareAndSet(14, 20));//false 122 System.out.println("integer :" + integer);//15 123 } 124 125 /** 126 * 测试 atomicLong 127 */ 128 private static void atomicLongTest() { 129 AtomicLong value = new AtomicLong(1L); 130 System.out.println("get:" + value.get());//1 131 System.out.println("getAndIncrement:" + value.getAndIncrement());//1 132 System.out.println("incrementAndGet:" + value.incrementAndGet());//3 133 System.out.println("getAndSet(10):" + value.getAndSet(10l));//3 134 System.out.println("compareAndSet(10,15):" + value.compareAndSet(10, 15));//true 135 System.out.println("compareAndSet(14,20):" + value.compareAndSet(14, 20));//false 136 System.out.println("AtomicLong:" + value);//15 137 138 } 139 140 /** 141 * 测试 atomicBoolean 142 */ 143 private static void atomicBooleanTest() { 144 AtomicBoolean value = new AtomicBoolean(false); 145 System.out.println("value:" + value.get());//false 146 System.out.println("getAndSet:" + value.getAndSet(true));//false 147 System.out.println("compareAndSet(t,f):" + value.compareAndSet(true, false));//true 148 System.out.println("AtomicBoolean:" + value);//false 149 } 150 151 /** 152 * 测试atomicReference 153 */ 154 private static void atomicRefTest() { 155 AtomicReference<AtomicTest.Person> value = new AtomicReference<Person>(new AtomicTest.Person(15, "a")); 156 Person a = value.get(); 157 Person b = new Person(11, "b"); 158 System.out.println("current name :" + a.getName());//a 159 System.out.println("current name getAndSet:" + value.getAndSet(b).getName());//a 160 System.out.println("current name " + value.get().getName());//b 161 } 162 163 164 /** 165 * 测试AtomicIntegerArray 166 */ 167 private static int[] intArray = {1, 2, 3, 4, 5}; 168 169 private static void atomicIntegerArrayTest() { 170 AtomicIntegerArray array = new AtomicIntegerArray(intArray); 171 array.getAndAdd(0, 10); 172 array.getAndSet(1, 10); 173 array.incrementAndGet(2); 174 System.out.println(array.get(0));//11 175 System.out.println(array.get(1));//10 176 System.out.println(array.get(2));//4 177 System.out.println(array.compareAndSet(0, 11, 10086));//true 178 System.out.println(array.get(0));//10086 179 } 180 181 /** 182 * 测试 AtomicLongArray 183 */ 184 private static void atomicLongArrayTest() { 185 //长度 构造函数 初始化为{0,0,0,0,0.....} 186 AtomicLongArray arr = new AtomicLongArray(10);//{0,0,0,0....} 187 System.out.println("get(index):" + arr.get(0)); //0 188 System.out.println(arr.incrementAndGet(7));//1 189 System.out.println(arr.decrementAndGet(5));//-1 190 System.out.println(arr.getAndIncrement(8));//0 191 System.out.println(arr.compareAndSet(9, 0, 1));//true 192 } 193 194 195 /** 196 * 测试AtomicReferenceArray 197 */ 198 private static void atomicRefArrayTest() { 199 AtomicReferenceArray<Person> arr = new AtomicReferenceArray<Person>(5);//[null, null, null, null, null] 200 System.out.println(arr.get(0)); 201 Person a = new Person(1, "a"); 202 Person b = new Person(2, "b"); 203 arr.getAndSet(0, a); 204 System.out.println(arr.get(0).getName());//a 205 System.out.println(arr.compareAndSet(0, a, b));//true 206 System.out.println(arr.get(0).getName());//b 207 } 208 209 210 /** 211 * 测试 AtomicIntegerFieldUpdater 212 */ 213 private static void atomicIntegerFieldUpdaterTest() { 214 Person a = new Person(1); 215 AtomicIntegerFieldUpdater<Person> updater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "length"); 216 System.out.println(updater.get(a));//1 217 System.out.println(updater.compareAndSet(a, 1, 180));//true 218 System.out.println(updater.get(a));//180 219 } 220 221 /** 222 * 测试 AtomicLongFieldUpdater 223 */ 224 private static void atomicLongFieldUpdaterTest() { 225 Person a = new Person(180l); 226 AtomicLongFieldUpdater<Person> updater = AtomicLongFieldUpdater.newUpdater(Person.class, "tall"); 227 System.out.println(updater.get(a));//180 228 System.out.println(updater.compareAndSet(a, 180l, 190l));//true 229 System.out.println(updater.get(a));//190 230 } 231 232 /** 233 * 测试 AtomicReferenceFieldUpdater 234 */ 235 private static void atomicRefFieldUpdterTest() { 236 Person p = new Person(); 237 AtomicReferenceFieldUpdater<Person, Student> ref = AtomicReferenceFieldUpdater.newUpdater(Person.class, Student.class, "student"); 238 Student a = new Student(2l); 239 Student defaultStudent = ref.get(p); 240 System.out.println(ref.get(p).getId());//1 241 System.out.println(ref.compareAndSet(p, defaultStudent, a));//true 242 System.out.println(ref.get(p).getId());//2 243 } 244 245 246 /** 247 * 测试 AtomicMarkableRef 248 * 相当于AtomicReference 和 Boolean mark 2个字段的包装体 249 */ 250 private static void atomicMarkableRefTest() { 251 Person a = new Person(11, "a"); 252 Person b = new Person(1, "b"); 253 AtomicMarkableReference<Person> ref = new AtomicMarkableReference<Person>(a, false); 254 boolean[] arr1 = {false}; 255 System.out.println(ref.getReference().getName());//a 256 System.out.println(ref.get(arr1));//a 257 System.out.println(ref.attemptMark(a, false));//true 258 System.out.println(ref.isMarked());//false 259 System.out.println(ref.compareAndSet(a, b, false, true));//true 260 System.out.println(ref.getReference().getName());//b 261 System.out.println(ref.isMarked());//true 262 } 263 264 265 /** 266 * 测试 AtomicStampedRef 267 * 相当于atomicReference 和一个Integer 的包装 268 */ 269 private static void atomicStampedRefTest() { 270 271 Person a = new Person(11, "a"); 272 Person b = new Person(1, "b"); 273 AtomicStampedReference<Person> ref = new AtomicStampedReference<Person>(a, 0); 274 int[] arr = {10}; 275 System.out.println(ref.getReference().getName());//a 276 System.out.println(ref.getStamp());//0 277 System.out.println(ref.get(arr));//a 278 System.out.println(ref.attemptStamp(a, 10));//true 279 System.out.println(ref.getStamp());//10 280 System.out.println(ref.compareAndSet(a, b, 10, 10086));//true 281 System.out.println(ref.getStamp());//10086 282 } 283 284 }