并发编程的目的:充分利用CPU资源
JUC:java.util.concurrent
进程与线程 Java代码无权直接创建线程,需要调用原生的C++方法start0()
才能创建线程
线程状态
实现多线程的两个接口:Callable和Runnable,一般来说Callable性能更高
Java中Thread类中的枚举类型:
1 2 3 4 5 6 7 8 public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
wait/sleep的区别
wait是Object的方法,sleep是Thread类的方法
wait会释放锁,sleep不会释放锁
wait必须在同步代码块中使用,而sleep可以在任何地方使用
锁 synchronized synchronized的本质:锁+队列
给方法添加synchronized锁的对象为方法的调用者,即方法所在的类
在使用synchronized的时候,有些情况下会出现虚假唤醒,为了防止这个问题,应该使用while来代替if判断,如:
1 2 3 while (number != 0 ){ this .wait(); }
使用方法 在定义接口方法时不能使用synchronized关键字
修饰方法
1 2 3 class Phone { public synchronized void sendSms () {...}; }
或
1 2 3 4 5 class Phone { public void sendSms () { synchronized (this ) {...} }; }
修饰static方法
1 2 3 class Phone { public static synchronized void sendSms () {...}; }
修饰类
1 2 3 4 5 class Phone { public void sendSms () { synchronized (Phone.class) {...} }; }
static锁与方法锁的区别 1 2 3 4 class Phone { public synchronized void sendSms () {...}; public synchronized void sendSms () {...}; }
这里的synchronized锁的是phone的实例对象,同一个对象调用不同方法会被锁
1 2 3 4 class Phone { public static synchronized void sendSms () {...}; public static synchronized void sendSms () {...}; }
这里的synchronized锁的是phone的静态模板phone.class,调用同时带有static的和synchronized的方法会被锁
Lock
公平锁:必须按先来后到顺序执行
1 ReentrantLock reentrantLock = new ReentrantLock(true );
非公平锁:可以插队
lock锁默认非公平锁
lock锁 常用实现类ReentrantLock
使用流程:
创建锁ReentrantLock lock = new ReentrantLock();
加锁lock.lock();
或lock.tryLock();
编写业务代码
解锁lock.unlock();
1 2 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 32 33 34 public class demo { public synchronized static void main (String[] args) { TicketClass ticket = new TicketClass(); new Thread(()-> { for (int i = 0 ; i < 20 ; i++) { ticket.getTicket(); } }, "消费者A" ).start(); new Thread(()-> { for (int i = 0 ; i < 20 ; i++) { ticket.getTicket(); } }, "消费者B" ).start(); new Thread(()-> { for (int i = 0 ; i < 20 ; i++) { ticket.getTicket(); } }, "消费者C" ).start(); } } class TicketClass { int i = 50 ; ReentrantLock lock = new ReentrantLock(); public void getTicket () { lock.lock(); if (i > 0 ) System.out.println(Thread.currentThread().getName() + "买到了第" + (50 - --i) +"张票,剩余" + i + "张票" ); lock.unlock(); } }
synchronized与Lock的区别
synchronized是内置的java关键字,Lock是一个java类
synchronized无法判断锁的状态,Lock可以判断是否获得到了锁
synchronized会自动释放,Lock必须手动释放
如果使用synchronized,正在运行的线程阻塞了,正在等待的线程会持续等待,而Lock不一定(使用tryLock方法)
synchronized可重入,不能中断,非公平;lock可重入,可以判断锁,可以自己设置是否公平锁
synchronized适合锁少量同步代码,lock适合锁大量同步代码
Condition lock.newCondition()
创建信号量
1 2 lock.await(); lock.signal()
可以通过信号量来指定线程执行顺序
读写锁
独占锁:写锁,只允许一个线程操作
共享锁:读锁,只允许读不允许写
1 2 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 32 33 34 35 36 37 38 39 public class Demo06 { public static void main (String[] args) { ReadWriteTest readWriteTest = new ReadWriteTest(); for (int i = 0 ; i < 6 ; i++) { final int temp = i; new Thread(() -> { readWriteTest.put(temp+"" ,temp+"" ); }, String.valueOf(i)).start(); } for (int i = 0 ; i < 6 ; i++) { final int temp = i; new Thread(() -> { readWriteTest.get(temp+"" ); }, String.valueOf(i)).start(); } } } class ReadWriteTest { private volatile Map<String, String> map = new HashMap<>(); ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void put (String key, String value) { lock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + "正在准备写入" ); map.put(key, value); System.out.println(Thread.currentThread().getName() + "正在准备写入" ); lock.writeLock().unlock(); } public void get (String key) { lock.readLock().lock(); System.out.println(Thread.currentThread().getName() + "正在准备读出" ); String s = map.get(key); System.out.println(Thread.currentThread().getName() + "读出" + s); lock.readLock().unlock(); } }
可重入锁 某个线程已经获取锁了,仍然可以获取锁而不死锁
1 2 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 public class Demo15 { public static void main (String[] args) { new Thread(() -> PhoneService.sms()).start(); new Thread(() -> PhoneService.call()).start(); } } class PhoneService { public static synchronized void sms () { try { call(); System.out.println(Thread.currentThread().getName() + " is sending sms..." ); TimeUnit.SECONDS.sleep(2 ); } catch (InterruptedException e) { e.printStackTrace(); } } public static synchronized void call () { System.out.println(Thread.currentThread().getName() + " is calling..." ); try { TimeUnit.SECONDS.sleep(2 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
这里的测试中,在0号线程保持sms方法锁的时间内,1号线程不能获取到call方法的锁
lock锁同理,可以对同一个lock多次加锁,但必须释放同样多次数的锁才能完全释放锁
自旋锁 以Unsafe类中的getAndSetInt为例,不断进行CAS循环直到成功的代码块即为自旋锁
1 2 3 4 5 6 7 8 public final int getAndSetInt (Object var1, long var2, int var4) { int var5; do { var5 = this .getIntVolatile(var1, var2); } while (!this .compareAndSwapInt(var1, var2, var5, var4)); return var5; }
死锁排查
使用jps -l
查看进程号
使用jstack 进程号
查看详细信息
集合类不安全 在使用ArrayList,HashSet或HashMap进行多线程操作时是不安全的,报错ConcurrentModificationException,如何解决?
使用Vector代替ArrayList,本质synchronized修饰
使用Collections集合类中的方法添加synchronized关键词
1 List<Object> list = Collections.synchronizedList(new ArrayList<>());
底层使用的是Lock锁的实现类
CopyOnWriteArrayList
CopyOnWriteArraySet
底层使用synchronized锁node结点的实现类ConcurrentHashMap
Callable 相较于Runnable:
可以有返回值
可以抛出异常
多次调用有缓存,比如下面这个例子call方法只会执行一次
使用方式
1 2 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 public class Demo02 { public static void main (String[] args) { TestCallable testThread = new TestCallable(); FutureTask<String> task = new FutureTask<>(testThread); new Thread(task, "A" ).start(); new Thread(task, "B" ).start(); try { System.out.println(task.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class TestCallable implements Callable <String > { @Override public String call () throws Exception { return Thread.currentThread().getName() + "test" ; } }
常用辅助类(AQS) AQS即AbstractQueuedSynchronizer,是一个常用的同步锁模板框架,常见的ReentrantLock、ReadWriteLock、CountDownLatch、CyclicBarrier、Semaphore都是AQS的实现类
维护一个state变量,只有state=0时其他线程才能成功获取到这个锁,并根据具体情况将state更改为不同的值
条件队列+同步队列:当前线程获取同步失败,会将当前线程作为Node结点添加到队列尾部并阻塞、当state可用时,从同步队列的头部获取线程并进行同步
资源共享的方式:
独占型:ReentrantLock、ReadWriteLock
共享型:CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch 减法计数器,一般用于控制线程等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Demo03 { public static void main (String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6 ); for (int i = 0 ; i < 6 ; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + " => gou out" ); countDownLatch.countDown(); }, String.valueOf(i)).start(); } try { countDownLatch.await(); System.out.println("Close door" ); } catch (InterruptedException e) { e.printStackTrace(); } } }
CyclicBarrier 与CountDownLatch类似的功能,但更为强大,一般用于同步一组线程,设置一个屏障点,当一组线程中的最后一个线程到达屏障点时所有线程才能够继续执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo04 { public static void main (String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(7 , () -> { System.out.println("7777777777777!" ); }); for (int i = 0 ; i < 7 ; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName()); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }, String.valueOf(i)).start(); } } }
Semaphore java中的信号量,一般在限流中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo05 { public static void main (String[] args) { Semaphore semaphore = new Semaphore(3 ); for (int i = 0 ; i < 6 ; i++) { new Thread(() -> { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"停入车位" ); TimeUnit.SECONDS.sleep(2 ); System.out.println(Thread.currentThread().getName()+"离开车位" ); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } }, String.valueOf(i)).start(); } } }
阻塞队列 BlockingQueue接口:和List和Set同级,实现Collection接口
BlockingQueue为FIFO类型的队列,先进先出
常用类结构
ArrayBlockingQueue 数组阻塞队列,常用api:
使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static void main (String[] args) { ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3 ); blockingQueue.add("1" ); System.out.println(blockingQueue.element()); System.out.println(blockingQueue.remove()); blockingQueue.offer("2" ); System.out.println(blockingQueue.peek()); System.out.println(blockingQueue.poll()); try { blockingQueue.put("3" ); System.out.println(blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } try { blockingQueue.offer("4" ,2 , TimeUnit.SECONDS); System.out.println(blockingQueue.poll(2 , TimeUnit.SECONDS)); } catch (InterruptedException e) { e.printStackTrace(); } }
SynchronousQueue 同步队列:没有容量,进入一个元素必须等待取出之后才能在此存放,类似于容量为1的阻塞队列
线程池 三大方法 创建线程池的三个api
Executors.newSingleThreadExecutor()
单线程实例
Executors.newFixedThreadPool(n)
最大线程数量为n
Executors.newCachedThreadPool()
伸缩缓存线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Demo08 { public static void main (String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); for (int i = 0 ; i < 10 ; i++) { final int temp = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + " => " + temp); }); } threadPool.shutdown(); } }
七大参数 上面的三个api方法都是同过创建ThreadPoolExecutor对象来实现的,包括调用一些他们的重载方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public ThreadPoolExecutor (int corePoolSize, //核心线程池大小 int maximumPoolSize, //最大线程池大小 long keepAliveTime, //超时时间 TimeUnit unit, //超时单位 BlockingQueue<Runnable> workQueue, //阻塞队列 ThreadFactory threadFactory, //线程工厂 RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0 ) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null ) throw new NullPointerException(); this .acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this .corePoolSize = corePoolSize; this .maximumPoolSize = maximumPoolSize; this .workQueue = workQueue; this .keepAliveTime = unit.toNanos(keepAliveTime); this .threadFactory = threadFactory; this .handler = handler; }
线程池的线程运行过程
可运行的线程数量最初只有corePoolSize个,先进入的线程开始运行
当corePoolSize的线程都正在运行,不能添加新的线程时,这时进入的线程进入到阻塞队列workQueue中进行等待
当workQueue的空间也被占满,仍有新的线程进入时,线程池会开放可同时运行的线程数量上限,最大为maximumPoolSize
当同时运行maximumPoolSize个线程,而workQueue的所有空间也都被占满时,线程池会遵循拒绝策略RejectedExecutionHandler,拒绝新线程的进入
拒绝策略的实现类
AbortPolicy:直接拒绝并抛出异常
CallerRunsPolicy:让调用线程的线程来运行(比如main线程新建的线程超过了上限,就会使用main线程来运行新线程的内容)
DiscardOldestPolicy:尝试让新线程来替代线程池中最老的线程,不会抛出异常
DiscardPolicy:直接拒绝但不抛出异常
设置maximumPoolSize的策略
自定义线程池 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Demo09 { public static void main (String[] args) { ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3 ); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2 , 5 , 2 , TimeUnit.SECONDS, blockingQueue, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); for (int i = 0 ; i < 9 ; i++) { poolExecutor.execute(()->{ System.out.println(Thread.currentThread().getName() + " 正在运行" ); }); } poolExecutor.shutdown(); } }
函数式接口 只有一个方法的接口,如Runnable接口
1 2 3 4 @FunctionalInterface public interface Runnable { public abstract void run () ; }
作用:简化编程模型,新版框架底层大量应用
Function 函数型接口:指定一个输入和一个输出
1 2 3 4 5 @FunctionalInterface public interface Function <T , R > { R apply (T t) ; ... }
Predicate 断定性接口:指定一个输入,返回布尔值
1 2 3 4 5 @FunctionalInterface public interface Predicate <T > { boolean test (T t) ; ... }
Consumer 消费型接口:只有输入,没有返回值
1 2 3 4 5 @FunctionalInterface public interface Consumer <T > { void accept (T t) ; ... }
Supplier 供给型接口:没有输入,只有输出
1 2 3 4 @FunctionalInterface public interface Supplier <T > { T get () ; }
Stream流式计算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Demo10 { public static void main (String[] args) { User user1 = new User(1 , "a" , 21 ); User user2 = new User(2 , "b" , 22 ); User user3 = new User(3 , "c" , 23 ); User user4 = new User(4 , "d" , 24 ); User user5 = new User(5 , "e" , 25 ); List<User> list = Arrays.asList(user1, user2, user3, user4, user5); list.stream() .filter(u->u.getId()%2 ==0 ) .filter(u->u.getAge()>23 ) .map(u->u.getName().toUpperCase()) .sorted((u1,u2)->u2.compareTo(u1)) .limit(1 ) .forEach(System.out::println); } }
ForkJoin 大数据量的情境下并行执行任务,提高效率
特点:工作窃取
假设两个线程:A处理速度较慢,B处理速度较快,当B处理完他自己队列中的所有任务之后,会尝试“窃取”A未完成队列中的任务进行工作
用法示例
1 2 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Demo11 { private static final Integer MAX = 1000 ; static class MyForkJoinTask extends RecursiveTask <Integer > { private Integer startValue; private Integer endValue; public MyForkJoinTask (Integer startValue , Integer endValue) { this .startValue = startValue; this .endValue = endValue; } @Override protected Integer compute () { if (endValue - startValue < MAX) { System.out.println("开始计算的部分:startValue = " + startValue + ";endValue = " + endValue); Integer totalValue = 0 ; for (int index = this .startValue ; index <= this .endValue ; index++) { totalValue += index; } return totalValue; } else { MyForkJoinTask subTask1 = new MyForkJoinTask(startValue, (startValue + endValue) / 2 ); subTask1.fork(); MyForkJoinTask subTask2 = new MyForkJoinTask((startValue + endValue) / 2 + 1 , endValue); subTask2.fork(); return subTask1.join() + subTask2.join(); } } } public static void main (String[] args) { ForkJoinPool pool = new ForkJoinPool(); long startTime = System.currentTimeMillis(); ForkJoinTask<Integer> taskFuture = pool.submit(new MyForkJoinTask(1 ,100_0000 )); try { Integer result = taskFuture.get(); long endTime = System.currentTimeMillis(); System.out.println("执行花费时间:" + (float )(endTime-startTime)/1000 + "s" ); System.out.println("result = " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(System.out); } } }
CompletableFuture 针对Future接口做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
1 2 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 32 33 34 public class Demo12 { public static void main (String[] args) throws Exception { CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Demo12::fetchPrice); cf.whenComplete((t,u)->{ System.out.println("执行结果:" + t); System.out.println("异常信息:" + u); }); cf.thenAccept((result) -> { System.out.println("price: " + result); }); cf.exceptionally((e) -> { e.printStackTrace(); return null ; }); Thread.sleep(200 ); } static Double fetchPrice () { try { Thread.sleep(100 ); } catch (InterruptedException e) { } if (Math.random() < 0.3 ) { throw new RuntimeException("fetch price failed!" ); } return 5 + Math.random() * 20 ; } }
JMM Java内存模型,是一种抽象概念
一些JMM同步约定:
线程解锁前,必须把共享变量立即写回主存中
线程加锁前,必须读取主存中最新的值保存在自身的工作内存中
八种内存间的交互操作
下图中除了6个操作,还有lock和unlock两个关于锁的操作
lock(锁定):作用于主内存的变量;它把这一个变量标识为一个线程独占的状态
unlock(解锁):作用域主内存的变量,把一个变量释放
read(读取):作用于主内存的变量,把一个变量的值从主内存传输到工作内存中,以便随后的load动作使用
load(载入):作用于工作内存变量,它把read曹祖从主内存中得到的变量值放入工作内存的变量副本中
use(适应):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
assign(赋值):作用于工作内存的变量,把一个执行引擎接收到值赋给工作的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store(存储):作用域工作内存的变量,它把工作内存中一个变量的值传递到主内存,以便随后的write操作
write(写入):作用于主内存的变量,它把store操作从工作内存得到的变量的值放入主内存的变量中
这八种操作中,read和load,store和write需要成对出现
Volatile 是JVM提供的轻量级的同步机制
保证可见性
添加Volatile关键字的属性对所有线程可见
不保证原子性
保证有序性(禁止指令重排)
如何保证原子性 使用原子性的类来替代原本的类,如:
1 private volatile static AtomicInteger num = new AtomicInteger();
原子类的方法都是调用native方法的CAS
指令重排 计算机在执行指令时可能会对程序代码的执行顺序作一定的修改,提高运行效率。
下面这种情况中,线程A和线程B自身的两行代码的执行顺序对于自身线程的结果来说没有任何影响,然而多线程的情况下可能会造成解决过的错误:
Volatile实现有序性的原理 Volatile关键字会添加一个lock锁的前缀指令
lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
它会强制将对缓存的修改操作立即写入主存
如果是写操作,它会导致其他CPU中对应的缓存行无效
单例模式 最大的特点:构造器私有
饿汉式 提前实例化对象,可能会浪费空间
1 2 3 4 5 6 7 8 public class Hungry { private final static Hungry HUNGRY = new Hungry(); private Hungry () {} public static Hungry getInstance () { return HUNGRY; } }
懒汉式 在获取实例时,对象不存在再实例化对象
1 2 3 4 5 6 7 8 9 10 11 public class Lazy { private static Lazy lazy; private Lazy () {} public static Lazy getInstance () { if (lazy == null ){ lazy = new Lazy(); } return lazy; } }
内部类模式 能达到双检锁方式一样的功效,但实现更简单
这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
1 2 3 4 5 6 7 8 9 10 public class Holder { private Holder () {} public static Holder getInstance () { return InnerClass.HOLDER; } public static class InnerClass { private static final Holder HOLDER = new Holder(); } }
DCL懒汉式 采用双锁机制,安全且在多线程情况下能保持高性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class LazyDCL { private static LazyDCL lazyDCL; private LazyDCL () {} public static LazyDCL getInstance () { if (lazyDCL == null ){ synchronized (LazyDCL.class){ if (lazyDCL == null ){ lazyDCL = new LazyDCL(); } } } return lazyDCL; } }
枚举 简洁,自动支持序列化机制,绝对防止多次实例化。
1 2 3 4 5 6 public enum EnumSingle { INSTANCE; public EnumSingle getInstance () { return INSTANCE; } }
测试:
1 2 3 4 5 6 public static void main (String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int .class); constructor.setAccessible(true ); EnumSingle instance = constructor.newInstance(); System.out.println(instance); }
结果:不允许利用反射获取实例:java.lang.IllegalArgumentException: Cannot reflectively create enum objects
原子性操作相关问题 CAS 原子类的方法compareAndSet,底层是使用Unsafe类的操作内存单元中的内容,具有原子性
1 2 public final boolean compareAndSet (int expect, int update)
使用
1 2 AtomicInteger atomicInteger = new AtomicInteger(2021 ); atomicInteger.compareAndSet(0 ,1 );
ABA问题 设int num = 0
假设A线程最初读取num为0,
这时B线程读取并修改num修为1,然后经过一些操作后又将num修改为0
这时A线程读取num仍然为0,但是这时的num已经不是当初未作任何修改的num了,A线程对于num经历的修改毫不知情
解决方法:使用带版本号的原子操作
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Demo14 { public static void main (String[] args) { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1 , 1 ); int stamp = atomicStampedReference.getStamp(); System.out.println("初始版本号:" + stamp); atomicStampedReference.compareAndSet(1 ,2 , stamp,atomicStampedReference.getStamp() + 1 ); System.out.println("修改后的版本号:" + atomicStampedReference.getStamp()); } }