Skip to content
鼓励作者:欢迎打赏犒劳

JAVA并发

volatile

Java 内存模型 所有的变量都存储在主内存中。 每个线程也会有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程不能直接读写主内存中的变量。

然而,Java 内存模型会带来一个新的问题,那就是内存可见性问题,也就是当某个线程修改了主内存中共享变量的值之后,其他线程不能感知到此值被修改了,它会一直使用自己工作内存中的“旧值”,这样程序的执行结果就不符合我们的预期了,这就是内存可见性问题

所以,volatile的作用就是强制线程直接读取主内存的变量值。

创建线程的几种方式

java
public class MyThread extends Thread{//继承Thread类

  public void run(){
    //重写run方法
  }
}

public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}
java
/*实现Runnable接口*/
private class UseRun implements Runnable{
    @Override
    public void run() {
        System.out.println("I am implements Runnable");
    }
    public static void main(String[] args) {
        UseRun useRun = new UseRun();
        new Thread(useRun).start();
    }
}
java
/*实现Callable接口,允许有返回值*/
private static class UseCall implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("I am implements Callable");
        return "CallResult";
    }
    
    public static void main(String[] args) {
        //第一步,先new一个类
        UseCall useCall = new UseCall();
        //第二步,用FutureTask类包装下,注意:FutureTask类继承了Runnable接口
        FutureTask<String> futureTask = new FutureTask<>(useCall);
        //第三步,启动
        new Thread(futureTask).start();
        //第四步,得到线程返回的结果,注意这里是阻塞式的。必须执行完线程才能拿到结果
        System.out.println(futureTask.get());
    }
    
}

AQS

AQS全称是AbstractQueuedSynchronizer,AQS是多线程同步器,它是J.U.C包中多个组件的底层实现,如Lock、CountDownLatch、Semaphore等都用到了AQS。

从本质上来说,AQS提供了两种锁机制,分别是排它锁和共享锁。

排它锁就是存在多线程竞争同一共享资源时,同一时刻只允许一个线程访问该共享资源,也就是多个线程中只能有一个线程获得锁资源,比如Lock中的ReentrantLock重入锁实现就是用到了AQS中的排它锁功能。

共享锁也称为读锁,就是在同一时刻允许多个线程同时获得锁资源,比如CountDownLatch和Semaphore都是用到了AQS中的共享锁功能。

原理:

用一个 volatile int 类型的 state 变量来表示同步状态,通过一个内置的 FIFO 双向队列来完成资源获取线程的排队工作。

state是锁的灵魂,判断是否获取锁成功,将所有暂时获取不到锁/资源的线程封装成 Node 节点,放入队列中排队等待。

比如:ReentrantLock: state 表示独占锁的持有计数(0=未锁定,>0=被锁定,>1=重入次数)。

CAS

CAS(Compare-And-Swap),即比较并交换,是一种无锁的、乐观的并发原子操作。它保证了一个线程在更新一个变量时,只有当变量的预期值和内存中的实际值相同时,才会将新值写入。

Java中主要通过 sun.misc.Unsafe 类提供的底层CAS方法(JVM内部使用)来实现。开发者最常接触的是 java.util.concurrent.atomic 包下的原子类,

例如:

  • AtomicInteger
  • AtomicLong
  • AtomicReference

示例:AtomicIntegerincrementAndGet()

java
AtomicInteger count = new AtomicInteger(0);

public void safeIncrement() {
    // 内部基于CAS实现,即使多线程调用也安全
    count.incrementAndGet(); 
}

它的内部实现类似于一个自旋循环:

java
public final int incrementAndGet() {
    for (;;) { // 自旋
        int current = get(); // 获取当前值 A
        int next = current + 1; // 计算新值 B
        if (compareAndSet(current, next)) // 核心CAS操作
            return next; // 成功则返回
    } // 失败则循环重试
}

优点:

  • 高性能:避免了重量级锁(如synchronized)带来的线程阻塞、上下文切换的开销,在竞争不激烈的情况下性能远超加锁。
  • 无死锁:由于是乐观重试,不存在死锁问题。

缺点:

  • ABA问题:一个值初始是A,中途被改为B,后又改回A。CAS检查时会误以为它没变,从而成功操作。解决方案是使用带版本号的原子引用类 AtomicStampedReference。
  • 循环时间长开销大:在高竞争环境下,如果线程一直重试,会空耗CPU资源。

ABA 问题

解决 ABA 问题的一种方法是使用带版本号的 CAS,也称为双重 CAS(Double CAS)或者版本号 CAS。具体来说,每次进行 CAS 操作时,不仅需要比较要修改的内存地址的值与期望的值是否相等,还需要比较这个内存地址的版本号是否与期望的版本号相等。如果相等,才进行修改操作。这样,在修改后的值后面追加上一个版本号,即使变量的值从 A 变成了 B 再变成了 A,版本号也会发生变化,从而避免了误判。

以下是一个使用 AtomicStampedReference 来解决 ABA 问题的示例代码:

java
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {

    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("初始值:" + atomicStampedRef.getReference() + ",版本号:" + atomicStampedRef.getStamp());

        // 线程 1 先执行一次 CAS 操作,期望值为 1,新值为 2,版本号为 0
        Thread thread1 = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1);
        });

        // 线程 2 先 sleep 1 秒,让线程 1 先执行一次 CAS 操作,然后再执行一次 CAS 操作,期望值为 2,新值为 1,版本号为 1
        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            int stamp = atomicStampedRef.getStamp();
            atomicStampedRef.compareAndSet(2, 1, stamp, stamp + 1);
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("最终值:" + atomicStampedRef.getReference() + ",版本号:" + atomicStampedRef.getStamp());
    }
}

以上程序的执行结果为:

初始值:1,版本号:0
最终值:1,版本号:2

如有转载或 CV 的请标注本站原文地址