java多线程之-CAS无锁-常见API
1.背景
这一节,就是学习常用的cas对象与api
.....
2.原子整数
直接看代码吧,或者看API文档
2.1.AtomicInteger的API演示
package com.ldp.demo06Atomic; import java.util.concurrent.atomic.AtomicInteger; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/16 9:15 * @description <p> * 原子整数 * JUC 并发包提供了: * AtomicBoolean * AtomicInteger * AtomicLong * </p> */ public class Test01 { public static void main(String[] args) { AtomicInteger i = new AtomicInteger(10); // 1. System.out.println("i=" + i.get() + ",执行i.getAndIncrement(),结果为:" + i.getAndIncrement() + ",实际i=" + i.get()); System.out.println("i=" + i.get() + ",执行i.incrementAndGet(),结果为:" + i.incrementAndGet() + ",实际i=" + i.get()); // 2. System.out.println("i=" + i.get() + ",执行i.getAndDecrement(),结果为:" + i.getAndDecrement() + ",实际i=" + i.get()); System.out.println("i=" + i.get() + ",执行i.decrementAndGet(),结果为:" + i.decrementAndGet() + ",实际i=" + i.get()); // 3. System.out.println("i=" + i.get() + ",执行i.getAndAdd(10),结果为:" + i.getAndAdd(10) + ",实际i=" + i.get()); System.out.println("i=" + i.get() + ",执行i.addAndGet(10),结果为:" + i.addAndGet(10) + ",实际i=" + i.get()); //4. P表示i的值 System.out.println("i=" + i.get() + ",执行i.getAndUpdate(p -> p - 2),结果为:" + i.getAndUpdate(p -> p - 2) + ",实际i=" + i.get()); System.out.println("i=" + i.get() + ",执行i.updateAndGet(p -> p - 2),结果为:" + i.updateAndGet(p -> p - 2) + ",实际i=" + i.get()); //5. P表示原来的值i , X表示修改的值10 System.out.println("i=" + i.get() + ",执行i.getAndAccumulate(10, (p, x) -> p + x),结果为:" + i.getAndAccumulate(10, (p, x) -> p + x) + ",实际i=" + i.get()); System.out.println("i=" + i.get() + ",执行i.accumulateAndGet(10, (p, x) -> p + x),结果为:" + i.accumulateAndGet(10, (p, x) -> p + x) + ",实际i=" + i.get()); /** * 测试执行结果 * i=10,执行i.getAndIncrement(),结果为:10,实际i=11 * i=11,执行i.incrementAndGet(),结果为:12,实际i=12 * i=12,执行i.getAndDecrement(),结果为:12,实际i=11 * i=11,执行i.decrementAndGet(),结果为:10,实际i=10 * i=10,执行i.getAndAdd(10),结果为:10,实际i=20 * i=20,执行i.addAndGet(10),结果为:30,实际i=30 * i=30,执行i.getAndUpdate(p -> p - 2),结果为:30,实际i=28 * i=28,执行i.updateAndGet(p -> p - 2),结果为:26,实际i=26 * i=26,执行i.getAndAccumulate(10, (p, x) -> p + x),结果为:26,实际i=36 * i=36,执行i.accumulateAndGet(10, (p, x) -> p + x),结果为:46,实际i=46 */ } }
3.原子引用
2.2.AtomicReference的API演示
package com.ldp.demo06Atomic; import org.junit.Test; import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicReference; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/16 9:42 * @description <p> * 原子引用 * 为什么需要原子引用类型? * AtomicReference * AtomicMarkableReference * AtomicStampedReference * <p> * Stamp:标记印记 * </p> */ public class Test02 { @Test public void test01AtomicReference() { AtomicReference<BigDecimal> ref = new AtomicReference<>(new BigDecimal("3.14")); while (true) { BigDecimal prev = ref.get(); BigDecimal next = prev.add(new BigDecimal("0.2")); boolean b = ref.compareAndSet(prev, next); if (b) { System.out.println("修改成功,ref=" + ref.get()); break; } } } }
2.3.ABA问题与解决
package com.ldp.demo06Atomic; import com.common.MyThreadUtil; import java.util.concurrent.atomic.AtomicReference; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/18 8:14 * @description */ public class Test03ABA { private static AtomicReference<String> ref = new AtomicReference<>("A"); /** * ABA问题与解决 * 代码执行结果: * A修改为B-->true * B修改为A-->true * A修改为C-->true * * @param args */ public static void main(String[] args) { String prev = ref.get(); new Thread(() -> { // A 修改为B System.out.println("A修改为B-->" + ref.compareAndSet(ref.get(), "B")); // B修改为A System.out.println("B修改为A-->" + ref.compareAndSet(ref.get(), "A")); }, "ABA").start(); MyThreadUtil.sleep(1); // A修改为C System.out.println("A修改为C-->" + ref.compareAndSet(prev, "C")); } }
2.4.AtomicStampedReference解决ABA问题
package com.ldp.demo06Atomic; import com.common.MyThreadUtil; import java.util.concurrent.atomic.AtomicStampedReference; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/18 8:14 * @description */ public class Test03ABAStamped { private static AtomicStampedReference<String> ref = new AtomicStampedReference<String>("A", 1); /** * ABA问题解决思路 * <p> * 主线程仅能判断出共享变量的值与最初值 A 是否相同,不知到从 A 改为 B 又 改回 A 的情况 * 如果主线程希望:只要有其它线程【修改了】共享变量,那么自己的 cas 就算失败, * 这时,需要比较值和版本号 * <p> * 代码执行结果: * A修改为B-->true * B修改为A-->true * A修改为C-->false * * @param args */ public static void main(String[] args) { String prev = ref.getReference(); int stamp = ref.getStamp(); new Thread(() -> { // A 修改为B System.out.println("A修改为B-->" + ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1)); // B修改为A System.out.println("B修改为A-->" + ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1)); }, "ABA").start(); MyThreadUtil.sleep(1); // A修改为C System.out.println("A修改为C-->" + ref.compareAndSet(prev, "C", stamp, stamp + 1)); } }
View Code
2.5.AtomicMarkableReference解决ABA问题
package com.ldp.demo06Atomic; import com.common.MyThreadUtil; import java.util.concurrent.atomic.AtomicMarkableReference; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/18 8:14 * @description */ public class Test03Markable { private static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A", true); /** * ABA问题与解决 * 代码执行结果: * marked-1=true * A修改为B-->true * marked-2=false * B修改为A-->true * marked-3=true * A修改为C-->true * marked-4=false * 有时候,并不关心引用变量更改了几次,只是关心是否更改过,所以就有了AtomicMarkableReference * * @param args */ public static void main(String[] args) { String prev = ref.getReference(); System.out.println("marked-1=" + ref.isMarked()); new Thread(() -> { // A 修改为B System.out.println("A修改为B-->" + ref.compareAndSet(ref.getReference(), "B", true, false)); System.out.println("marked-2=" + ref.isMarked()); // B修改为A System.out.println("B修改为A-->" + ref.compareAndSet(ref.getReference(), "A", false, true)); }, "ABA").start(); MyThreadUtil.sleep(1); System.out.println("marked-3=" + ref.isMarked()); // A修改为C System.out.println("A修改为C-->" + ref.compareAndSet(prev, "C", true, false)); System.out.println("marked-4=" + ref.isMarked()); } }
4.原子数组
package com.ldp.demo06Atomic; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/18 8:55 * @description <p> * 原子数组 * AtomicIntegerArray * AtomicLongArray * AtomicReferenceArray * </p> */ public class Test04Array { /** * 测试自定义的数组 * int[10] 存在线程不安全 */ @Test public void test01() throws InterruptedException { myArray(() -> new int[10], (array) -> array.length, (array, index) -> array[index]++, (array) -> System.out.println(Arrays.toString(array))); } /** * 测试自定义的数组 * AtomicIntegerArray 线程安全 */ @Test public void test02() throws InterruptedException { myArray( () -> new AtomicIntegerArray(10), (array) -> array.length(), (array, index) -> array.getAndIncrement(index), array -> System.out.println(array) ); } /** * @param supplierArray 提供一个数组 * @param functionLength 获取数组长度 * @param addArray 数组元素自增1 * @param printArray 打印数组 * @param <T> 数组的泛型 */ public <T> void myArray(Supplier<T> supplierArray, Function<T, Integer> functionLength, BiConsumer<T, Integer> addArray, Consumer<T> printArray ) throws InterruptedException { // 定义一个放线程的集合 List<Thread> list = new ArrayList<>(); // 1.提供一个数组 T array = supplierArray.get(); // 2.获取数组长度的方法 Integer length = functionLength.apply(array); // 遍历数组,使用1000个线程,每个线程给每个元素加1 for (int i = 0; i < length; i++) { // 1000个线程,每个线程给每个元素加1 Thread t = new Thread(() -> { for (int j = 0; j < 10000; j++) { // 3.自增方法,传入array与下标index addArray.accept(array, j % length); } }); list.add(t); } // 启动所有线程 for (Thread thread : list) { thread.start(); } // 等待所有线程执行完 for (Thread thread : list) { thread.join(); } // 4.打印数组的方法 printArray.accept(array); } }
5.字段更新器
package com.ldp.demo06Atomic; import org.junit.Test; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/18 9:56 * @description <p> * 字段更新器 * AtomicReferenceFieldUpdater * AtomicIntegerFieldUpdater * AtomicLongFieldUpdater * </p> */ public class Test05Field { private volatile int age = 18; @Test public void test01() { AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Test05Field.class, "age"); // 创建Test05Field对象 Test05Field test05Field = new Test05Field(); // 将18修改为 30岁 fieldUpdater.compareAndSet(test05Field, test05Field.age, 30); System.out.println("当前年龄:" + test05Field.age); } }
6.原子累加器:LongAdder
package com.ldp.demo06Atomic; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; import java.util.function.Consumer; import java.util.function.Supplier; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/18 10:13 * @description */ @Slf4j public class Test06LongAdder { /** * AtomicLong 与 LongAdder 测试 * LongAdder:在多线程并发的情况下,LongAdder会明显优于AtomicLong * <p> * LongAdder的原理: * 在由线程竞争cpu时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]... * 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。 * <p> * 09:35:18.967 [main] -> start..... * num=10000000,耗时:172 毫秒 * num=10000000,耗时:203 毫秒 * num=10000000,耗时:187 毫秒 * num=10000000,耗时:188 毫秒 * num=10000000,耗时:171 毫秒 * 09:35:20.013 [main] -> ------------------------------ * num=10000000,耗时:157 毫秒 * num=10000000,耗时:93 毫秒 * num=10000000,耗时:63 毫秒 * num=10000000,耗时:78 毫秒 * num=10000000,耗时:78 毫秒 * 09:35:20.482 [main] -> end..... * * @throws InterruptedException */ @Test public void test01() throws InterruptedException { log.info("start....."); for (int i = 0; i < 5; i++) { methodAdd(() -> new AtomicLong(), (num) -> num.getAndIncrement()); } log.info("------------------------------"); for (int i = 0; i < 5; i++) { methodAdd(() -> new LongAdder(), (num) -> num.increment()); } log.info("end....."); } /** * 给一个数组累加 * * @param supplierNum 传入的数字 * @param addNum 数字加一 * @param <T> */ public <T> void methodAdd(Supplier<T> supplierNum, Consumer<T> addNum) throws InterruptedException { long start = System.currentTimeMillis(); List<Thread> list = new ArrayList<>(); T num = supplierNum.get(); // 3个线程 for (int i = 0; i < 100; i++) { Thread thread = new Thread(() -> { // 每个线程加 10万次 for (int j = 0; j < 100000; j++) { addNum.accept(num); } }); list.add(thread); } // 启动线程 for (Thread thread : list) { thread.start(); } // 等待线程结束 for (Thread thread : list) { thread.join(); } long end = System.currentTimeMillis(); System.out.println("num=" + num + ",耗时:" + ((end - start)) + " 毫秒"); } }
7.CAS实现自定义锁
package com.ldp.demo06Atomic; import java.util.concurrent.atomic.AtomicInteger; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 02/19 9:42 * @description <p> * 利用cas的特性实现自己的锁 * </p> */ public class Test07MyCasLock { AtomicInteger lock = new AtomicInteger(0); /** * 获取锁 */ public void lock() { while (true) { if (lock.compareAndSet(0, 1)) { // 取锁成功 break; } } } /** * 释放锁 */ public void unlock() { lock.set(0); } /** * 测试自定义的锁 * 注意:自定义的锁只是练习对cas的理解,不作为生产的实现方式 * * @param args */ public static void main(String[] args) { // UnsafeAccessor. Test07MyCasLock casLock = new Test07MyCasLock(); casLock.lock(); try { System.out.println("加锁成功...."); } finally { casLock.unlock(); System.out.println("锁已经释放..."); } } }