From ad68c65ebce91255537132f649a32f3a9b8835a8 Mon Sep 17 00:00:00 2001 From: weewwccw <5572917+shifm@user.noreply.gitee.com> Date: Mon, 23 Dec 2019 09:21:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=9F=E5=AD=90=E7=B1=BB=E5=AD=A6=E4=B9=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week_02/11/AtomicStampedReference.md | 40 +++++++ week_02/11/LongAdder.md | 80 ++++++++++++++ week_02/11/Unsafe.md | 149 +++++++++++++++++++++++++++ week_02/11/atomicinteger.md | 144 ++++++++++++++++++++++++++ 4 files changed, 413 insertions(+) create mode 100644 week_02/11/AtomicStampedReference.md create mode 100644 week_02/11/LongAdder.md create mode 100644 week_02/11/Unsafe.md create mode 100644 week_02/11/atomicinteger.md diff --git a/week_02/11/AtomicStampedReference.md b/week_02/11/AtomicStampedReference.md new file mode 100644 index 0000000..79195be --- /dev/null +++ b/week_02/11/AtomicStampedReference.md @@ -0,0 +1,40 @@ + +//构造方法, 传入引用和戳 +public AtomicStampedReference(V initialRef, int initialStamp) +//返回引用 +public V getReference() +//返回版本戳 +public int getStamp() +//如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存 +public boolean compareAndSet(V expectedReference, + V newReference, + int expectedStamp, + int newStamp) +//如果当前引用 等于 预期引用, 将更新新的版本戳到内存 +public boolean attemptStamp(V expectedReference, int newStamp) +//设置当前引用的新引用和版本戳 + +public static void main(String[] args) { + + String str1 = "aaa"; + String str2 = "bbb"; + AtomicStampedReference reference = new AtomicStampedReference(str1,1); + reference.compareAndSet(str1,str2,reference.getStamp(),reference.getStamp()+1); + System.out.println("reference.getReference() = " + reference.getReference()); + + boolean b = reference.attemptStamp(str2, reference.getStamp() + 1); + System.out.println("b: "+b); + System.out.println("reference.getStamp() = "+reference.getStamp()); + + boolean c = reference.weakCompareAndSet(str2,"ccc",4, reference.getStamp()+1); + System.out.println("reference.getReference() = "+reference.getReference()); + System.out.println("c = " + c); + } + +输出: +reference.getReference() = bbb +b: true +reference.getStamp() = 3 +reference.getReference() = bbb +c = false + diff --git a/week_02/11/LongAdder.md b/week_02/11/LongAdder.md new file mode 100644 index 0000000..6ba7318 --- /dev/null +++ b/week_02/11/LongAdder.md @@ -0,0 +1,80 @@ +final void longAccumulate(long x, LongBinaryOperator fn, + boolean wasUncontended) { + int h; + if ((h = getProbe()) == 0) { + ThreadLocalRandom.current(); // force initialization + h = getProbe(); + wasUncontended = true; + } + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (cellsBusy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (cellsBusy == 0 && casCellsBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + cellsBusy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, ((fn == null) ? v + x : + fn.applyAsLong(v, x)))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (cellsBusy == 0 && casCellsBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h = advanceProbe(h); + } + else if (cellsBusy == 0 && cells == as && casCellsBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + cellsBusy = 0; + } + if (init) + break; + } + else if (casBase(v = base, ((fn == null) ? v + x : + fn.applyAsLong(v, x)))) + break; // Fall back on using base + } + } \ No newline at end of file diff --git a/week_02/11/Unsafe.md b/week_02/11/Unsafe.md new file mode 100644 index 0000000..2a0b2af --- /dev/null +++ b/week_02/11/Unsafe.md @@ -0,0 +1,149 @@ +Unsafe类是"final"的,不允许继承。且构造函数是private的: + +public final class Unsafe { + private static final Unsafe theUnsafe; + public static final int INVALID_FIELD_OFFSET = -1; + + private static native void registerNatives(); + // 构造函数是private的,不允许外部实例化 + private Unsafe() { + } + ... +} +因此我们无法在外部对Unsafe进行实例化。 + +获取Unsafe +Unsafe无法实例化,那么怎么获取Unsafe呢?答案就是通过反射来获取Unsafe: + +public Unsafe getUnsafe() throws IllegalAccessException { + Field unsafeField = Unsafe.class.getDeclaredFields()[0]; + unsafeField.setAccessible(true); + Unsafe unsafe = (Unsafe) unsafeField.get(null); + return unsafe; +} +主要功能 +Unsafe的功能如下图: + +Unsafe-xmind +普通读写 +通过Unsafe可以读写一个类的属性,即使这个属性是私有的,也可以对这个属性进行读写。 + +读写一个Object属性的相关方法 + +public native int getInt(Object var1, long var2); + +public native void putInt(Object var1, long var2, int var4); +getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。其他的primitive type也有对应的方法。 + +Unsafe还可以直接在一个地址上读写 + +public native byte getByte(long var1); + +public native void putByte(long var1, byte var3); +getByte用于从指定内存地址处开始读取一个byte。putByte用于从指定内存地址写入一个byte。其他的primitive type也有对应的方法。 + +volatile读写 +普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。 + +public native int getIntVolatile(Object var1, long var2); + +public native void putIntVolatile(Object var1, long var2, int var4); +getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int。putIntVolatile方法用于在对象指定偏移地址处volatile写入一个int。 + +volatile读写相对普通读写是更加昂贵的,因为需要保证可见性和有序性,而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。 + +有序写入 +有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。 + +public native void putOrderedObject(Object var1, long var2, Object var4); + +public native void putOrderedInt(Object var1, long var2, int var4); + +public native void putOrderedLong(Object var1, long var2, long var4); +直接内存操作 +我们都知道Java不可以直接对内存进行操作,对象内存的分配和回收都是由JVM帮助我们实现的。但是Unsafe为我们在Java中提供了直接操作内存的能力。 + +// 分配内存 +public native long allocateMemory(long var1); +// 重新分配内存 +public native long reallocateMemory(long var1, long var3); +// 内存初始化 +public native void setMemory(long var1, long var3, byte var5); +// 内存复制 +public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7); +// 清除内存 +public native void freeMemory(long var1); +CAS相关 +JUC中大量运用了CAS操作,可以说CAS操作是JUC的基础,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作: + +public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); + +public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); + +public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); +CAS一般用于乐观锁,它在Java中有广泛的应用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS来实现乐观锁。 + +偏移量相关 +public native long staticFieldOffset(Field var1); + +public native long objectFieldOffset(Field var1); + +public native Object staticFieldBase(Field var1); + +public native int arrayBaseOffset(Class var1); + +public native int arrayIndexScale(Class var1); +staticFieldOffset方法用于获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量。objectFieldOffset方法用于获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量。staticFieldBase方法用于返回Field所在的对象。arrayBaseOffset方法用于返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量。arrayIndexScale方法用于计算数组中第一个元素所占用的内存空间。 + +线程调度 +public native void unpark(Object var1); + +public native void park(boolean var1, long var2); + +public native void monitorEnter(Object var1); + +public native void monitorExit(Object var1); + +public native boolean tryMonitorEnter(Object var1); +park方法和unpark方法相信看过LockSupport类的都不会陌生,这两个方法主要用来挂起和唤醒线程。LockSupport中的park和unpark方法正是通过Unsafe来实现的: + +// 挂起线程 +public static void park(Object blocker) { + Thread t = Thread.currentThread(); + setBlocker(t, blocker); // 通过Unsafe的putObject方法设置阻塞阻塞当前线程的blocker + UNSAFE.park(false, 0L); // 通过Unsafe的park方法来阻塞当前线程,注意此方法将当前线程阻塞后,当前线程就不会继续往下走了,直到其他线程unpark此线程 + setBlocker(t, null); // 清除blocker +} + +// 唤醒线程 +public static void unpark(Thread thread) { + if (thread != null) + UNSAFE.unpark(thread); +} +monitorEnter方法和monitorExit方法用于加锁,Java中的synchronized锁就是通过这两个指令来实现的。 + +类加载 +public native Class defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6); + +public native Class defineAnonymousClass(Class var1, byte[] var2, Object[] var3); + +public native Object allocateInstance(Class var1) throws InstantiationException; + +public native boolean shouldBeInitialized(Class var1); + +public native void ensureClassInitialized(Class var1); +defineClass方法定义一个类,用于动态地创建类。 +defineAnonymousClass用于动态的创建一个匿名内部类。 +allocateInstance方法用于创建一个类的实例,但是不会调用这个实例的构造方法,如果这个类还未被初始化,则初始化这个类。 +shouldBeInitialized方法用于判断是否需要初始化一个类。 +ensureClassInitialized方法用于保证已经初始化过一个类。 + +内存屏障 +public native void loadFence(); + +public native void storeFence(); + +public native void fullFence(); +loadFence:保证在这个屏障之前的所有读操作都已经完成。 +storeFence:保证在这个屏障之前的所有写操作都已经完成。 +fullFence:保证在这个屏障之前的所有读写操作都已经完成。 diff --git a/week_02/11/atomicinteger.md b/week_02/11/atomicinteger.md new file mode 100644 index 0000000..ac9fb22 --- /dev/null +++ b/week_02/11/atomicinteger.md @@ -0,0 +1,144 @@ + +public class Sample1 { + + private static Integer count = 0; + + synchronized public static void increment() { + count++; + } + +} +复制代码 +以下是AtomicInteger的: + +复制代码 +public class Sample2 { + + private static AtomicInteger count = new AtomicInteger(0); + + public static void increment() { + count.getAndIncrement(); + } + +} +复制代码 +以上两段代码,在使用Integer的时候,必须加上synchronized保证不会出现并发线程同时访问的情况,而在AtomicInteger中却不用加上synchronized,在这里AtomicInteger是提供原子操作的,下面就对这进行相应的介绍。 + +AtomicInteger介绍 +AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。 + +AtomicInteger使用场景 +AtomicInteger提供原子操作来进行Integer的使用,因此十分适合高并发情况下的使用。 + +AtomicInteger源码部分讲解 +复制代码 +public class AtomicInteger extends Number implements java.io.Serializable { + private static final long serialVersionUID = 6214790243416807050L; + + // setup to use Unsafe.compareAndSwapInt for updates + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; +复制代码 +以上为AtomicInteger中的部分源码,在这里说下其中的value,这里value使用了volatile关键字,volatile在这里可以做到的作用是使得多个线程可以共享变量,但是问题在于使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用,因此AtomicInteger类不要随意使用,要在使用场景下使用。 + +AtomicInteger实例使用 +以下就是在多线程情况下,使用AtomicInteger的一个实例,这段代码是借用IT宅中的一段代码。 + +复制代码 +public class AtomicTest { + + static long randomTime() { + return (long) (Math.random() * 1000); + } + + public static void main(String[] args) { + // 阻塞队列,能容纳100个文件 + final BlockingQueue queue = new LinkedBlockingQueue(100); + // 线程池 + final ExecutorService exec = Executors.newFixedThreadPool(5); + final File root = new File("D:\\ISO"); + // 完成标志 + final File exitFile = new File(""); + // 原子整型,读个数 + // AtomicInteger可以在并发情况下达到原子化更新,避免使用了synchronized,而且性能非常高。 + final AtomicInteger rc = new AtomicInteger(); + // 原子整型,写个数 + final AtomicInteger wc = new AtomicInteger(); + // 读线程 + Runnable read = new Runnable() { + public void run() { + scanFile(root); + scanFile(exitFile); + } + + public void scanFile(File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(new FileFilter() { + public boolean accept(File pathname) { + return pathname.isDirectory() || pathname.getPath().endsWith(".iso"); + } + }); + for (File one : files) + scanFile(one); + } else { + try { + // 原子整型的incrementAndGet方法,以原子方式将当前值加 1,返回更新的值 + int index = rc.incrementAndGet(); + System.out.println("Read0: " + index + " " + file.getPath()); + // 添加到阻塞队列中 + queue.put(file); + } catch (InterruptedException e) { + + } + } + } + }; + // submit方法提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。 + exec.submit(read); + + // 四个写线程 + for (int index = 0; index < 4; index++) { + // write thread + final int num = index; + Runnable write = new Runnable() { + String threadName = "Write" + num; + + public void run() { + while (true) { + try { + Thread.sleep(randomTime()); + // 原子整型的incrementAndGet方法,以原子方式将当前值加 1,返回更新的值 + int index = wc.incrementAndGet(); + // 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。 + File file = queue.take(); + // 队列已经无对象 + if (file == exitFile) { + // 再次添加"标志",以让其他线程正常退出 + queue.put(exitFile); + break; + } + System.out.println(threadName + ": " + index + " " + file.getPath()); + } catch (InterruptedException e) { + } + } + } + + }; + exec.submit(write); + } + exec.shutdown(); + } + +} +复制代码 +AtomicInteger使用总结 +AtomicInteger是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合,但并不能每一种场景都适合,不同场景要使用使用不同的数值类。 \ No newline at end of file -- Gitee