【Java】JUC并发(CAS、ThreadLocal)

发布于:2025-07-25 ⋅ 阅读:(14) ⋅ 点赞:(0)

CAS 

1、概述

        CAS全称为" Compare And Swap ",中文为" 比较并交换 "。它是一种无锁非阻塞线程同步方式。同时相比于synchronize或者Lock来说,是一种轻量级的实现方案。

2、用途

        1、AtomicInteger

        AtomicInteger 是 Java 中 java.util.concurrent.atomic 包下的一个类,它提供了一种原子操作整数的方式,适用于多线程环境下的计数器等场景。源码:

public class AtomicInteger extends Number implements java.io.Serializable {

    // 使用 Unsafe 类提供的底层原子操作
    private static final Unsafe U = Unsafe.getUnsafe();

    // VALUE 字段存储 "value" 字段在 AtomicInteger 对象中的内存偏移量
    // 通过该偏移量,Unsafe 类可以直接访问和修改该字段
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

    // 使用 volatile 修饰保证内存可见性,任何修改都会立即刷新到主内存
    // 但不保证复合操作(如 i++)的原子性,需配合 Unsafe 的 CAS 操作
    private volatile int value;
}

        实现:

// 将count初始化为0
private static AtomicInteger count = new AtomicInteger(0);

2、ConcurrentHashMap

         ConcurrentHashMap 是 Java 中用于多线程环境下的高效哈希表实现,它解决了统 HashMap 在多线程环境下的线程安全问题,具有更高的并发性能。当没有哈希冲突时运用CAS来将数据存入哈希表。

3、执行流程

        假设有内存中有一个变量i,当任务开始时,会读取当前数值A,在经过逻辑计算出B也就是我们要存入的值;在存入之前会读取现在的值C,在和A进行比较:

        1、如果A与C相同:说明值在这之前没有被修改则将我们希望存入的值存入。

        2、如果A与C不相同:说明值在这之前已经被修改则不进行存入操作。

4、缺点

1、循环时间长,开销大

        在 Unsafe 的实现中使用的自旋锁机制,如果在比较环节一直无法成功,则会一直尝试进入一个死循环的状态导致CPU的开销极大。

2、只能保证一个共享变量的原子操作

        如果只存在一个共享变量使用CAS机制可以保证期原子性操作,但如果存在多个共享变量或者一整个代码块逻辑则就无法保证其原子性。此时我我们应该考虑采用加锁的方式来保证其原子性。

3、ABA问题

        由于CAS是一种无锁非阻塞的线程同步方式,就可能产生ABA问题:

  •  线程 P1 在共享变量中读到值为 A;
  •  线程 P1 被抢占,线程 P2 开始执行;
  •  线程 P2 将共享变量改为 B,又将共享变量 B 改为 A,线程 P2 被线程 P1 抢占开始执行;
  •  线程 P1 比较发现共享变量为开始读到的 A 则继续执行。

5、总结

        CAS 是无锁并发编程的核心技术,通过硬件原子性实现高效的线程安全。它适用于读多写少、竞争较轻的场景,能够显著减少锁的使用。但需注意 ABA 问题和自旋开销,并根据场景选择合适的解决方案。


ThreadLocal(线程局部变量)

1、概述

        ThreadLocal称为:线程局部变量,用于在线程保存数据。ThreadLocal利用 Thread 中的ThreadLocalMap来保存数据,另外由于保存的数据属于当前线程,所以该变量对于其他线程来说是隔离的,也就是当前线程独有的变量。

public class Test02 {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            threadLocal.set("喜洋洋");
            show();
            Sample.dosth();
        },"线程1");

        Thread t2 = new Thread(()->{
            threadLocal.set("美洋洋");
            show();
            Sample.dosth();
        },"线程2");

        t2.start();
        t1.start();
    }

    public static void show(){
        System.out.println("show:" + Thread.currentThread().getName() + "分配角色" + threadLocal.get());
    }
}
class Sample{
    public static void dosth(){
        System.out.println("show:" + Thread.currentThread().getName() + "分配角色" + Test02.threadLocal.get());
    }
}



show:线程1分配角色喜洋洋
show:线程2分配角色美洋洋
show:线程1分配角色喜洋洋
show:线程2分配角色美洋洋

2、 ThreadLocalMap结构

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            // 将ThreadLocal对象包装为弱引用(WeakReference的构造函数参数)
            super(k);
            value = v;
        }
    }
 
    //存储Entry的数组,根据需要扩容,数组长度必须始终为2的幂,以确保哈希算法的均匀性
    private Entry[] table;
}

 A、弱引用机制:

        Entry是对ThreadLocal的弱引用,所以他会被GC回收,当被回收后Key会变成null,但value不会改变,从而出现大量的null--value的键值对,从而会出现内存泄漏的情况,所以当我们使用完毕后可以调用remove()放对其进行回收。

B、哈希冲突的处理机制:

        ThreadLocalMap不会像前面所说的 ConcurrentHashMap 一样采用链表法来解决哈希冲突,而是采用开放地址法,当发生冲突时会寻找下一个合适的空位进行插入。

C、容量设计

        初始容量为16,且数组长度始终保持为2的幂,这样可以提高运算效率。

3、常用方法

1、数据存储set()

public class Test09 {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            threadLocal.set("小灰灰");
        },"数据写入线程");
    }
}

2、数据获取get()

public class Test09 {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            threadLocal.set("小灰灰");

            String s = threadLocal.get();
            System.out.println("子线程=>" + s);
        },"数据写入线程");

        t1.start();

        System.out.println("主线程=>" + threadLocal.get());

    }
}

主线程=>null
子线程=>小灰灰

// 小灰灰属于子线程写入,所以小灰灰该数据属于子线程,主线程无法拿到

3、数据删除remove()

public class Test09 {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            threadLocal.set("小灰灰");

            // 删除方法
            threadLocal.remove();

            String s = threadLocal.get();
            System.out.println("子线程=>" + s);
        },"数据写入线程");

        t1.start();

        System.out.println("主线程=>" + threadLocal.get());

    }
}

主线程=>null
子线程=>null

4、InheritableThreadLocal

        解决父子线程数据无法共享的问题。

public class Test09 {
    public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        threadLocal.set("小灰灰");

        Thread t1 = new Thread(()->{
            String s = threadLocal.get();

            System.out.println("子线程=>" + s);

        },"数据写入线程");

        t1.start();
        t1.join();

        System.out.println("主线程=>" + threadLocal.get());

    }
}


子线程=>小灰灰
主线程=>小灰灰

// 父子线程都可以拿到存入的数据

5、总结

        ThreadLocal 是实现线程隔离的高效工具,适用于需要在单个线程内维护状态的场景。通过合理使用 remove() 方法和理解其内部机制,可以有效避免内存泄漏,发挥其最大优势。


网站公告

今日签到

点亮在社区的每一天
去签到