【Java EE】CAS原理和实现以及JUC中常见的类的使用

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶​
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客

系列专栏:xiaoxie的JAVAEE学习系列专栏——CSDN博客●'ᴗ'σσணღ
我的目标:"团团等我💪( ◡̀_◡́ ҂)" 

( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​+关注(互三必回)!

目录

​编辑一.CAS

1.什么是CAS

2.CAS的原理

例子:抢座位游戏

如何用CAS解决:

 3.CAS出现的问题

4.CAS与锁的区别

1. 实现方式:

2. 性能特性:

3. 可伸缩性:

4. 编程复杂性:

5. 死锁和饥饿:

6.总结:

二.JUC(Java.util.concurrent)中常见类的使用

 1.ReentrantLock

1.ReentrantLock 的主要特点:

2.ReentrantLock 的基本使用 

3.ReentrantLock vs synchronized  

4.注意事项 

 2.原子类(AtomicXXX)

 3.Semaphore(信号量)

1.Semaphore 的基本概念

2.Semaphore 的使用场景:

3.Semaphore 的基本使用: 

4.Semaphore 的公平性 

5.注意事项:

 4.CountDownLatch

1.CountDownLatch 的主要特点:

2.CountDownLatch 的基本使用: 

3.注意事项:

4.运用场景(重点)

5.FutureTask 和 Callable

1.Callable 接口:

2.FutureTask 类:

3.使用示例:

主要特点:

注意事项:

6.CyclicBarrier

1.CyclicBarrier 的主要特点:

2.CyclicBarrier 的基本使用:

3.注意事项:

三.ThreadLocal的底层实现

1.ThreadLocal 的底层实现原理:

2.示例代码: 

3.注意事项:


一.CAS

1.什么是CAS

在Java中,CAS代表“Compare-And-Swap”,即比较并交换。这是一种用于实现多线程中无锁编程的原子操作。CAS操作通常由三个参数:内存地址V旧的预期值A要更新的新值B。只有当内存地址V的当前值与预期值A相等时,才将内存地址V的值更新为B。如果不相等,则不进行任何操作。

CAS操作通常用于实现线程安全的计数器、栈等数据结构,以及在并发编程中实现同步机制,如锁。CAS操作可以避免使用传统的锁机制,从而减少线程间的上下文切换,提高程序的执行效率。

2.CAS的原理

CAS(Compare-And-Swap)的原理基于一个简单的思想:在多线程环境中,当多个线程尝试同时修改共享数据时,CAS提供了一种机制来确保只有一个线程能够成功修改数据。CAS操作通常由处理器的原子指令直接支持,这意味着在执行CAS操作时,不会受到其他线程的干扰

CAS操作的三个主要步骤如下:

  1. 比较:首先,CAS检查内存中的值是否与预期值(expected value)相等。这个预期值是线程在执行CAS操作之前所知道的值。

  2. 交换:如果内存中的值与预期值相等,CAS将执行交换操作,即将内存中的值更新为新值(new value)。

  3. 返回结果:CAS操作完成后,会返回一个布尔值,指示操作是否成功。如果内存中的值与预期值不相等,CAS操作失败,返回false;如果内存中的值与预期值相等,并且值已被成功更新,返回true

CAS操作通常用于实现无锁数据结构和算法,因为它们可以避免使用传统的锁机制,从而减少线程间的上下文切换,提高程序的执行效率。

在Java中,java.util.concurrent.atomic包提供了多个原子类,如AtomicIntegerAtomicLongAtomicReference等,它们内部都使用了CAS操作来实现线程安全的原子操作。

以上的原理内容,确实有点枯燥难懂,博主这里举个小例子来说明一下吧

例子:抢座位游戏

想象一下,你和一群朋友在玩一个抢座位的游戏。游戏规则如下:

  1. 开始游戏:所有玩家围成一个圈,但圈里比玩家人数少一个座位。
  2. 音乐开始:当音乐开始播放时,所有玩家围绕座位走动。
  3. 音乐停止:一旦音乐停止,每个玩家都要尽快找到一个座位坐下。
  4. 抢座位:如果两个玩家同时试图坐同一个座位,他们需要通过一种方式来决定谁可以坐下。

在这个游戏中,我们可以将每个座位看作是一个共享资源,玩家则代表不同的线程。现在,我们用CAS的原理来解决两个玩家同时试图坐同一个座位的问题:

  1. 比较(Compare):每个玩家都会记住自己之前坐过的座位(预期值A)。

  2. 尝试坐下(Swap):当音乐停止时,每个玩家都会尝试去坐他们记住的那个座位。如果座位上没有人(当前值与预期值相等),玩家就可以坐下(将新值B,即玩家自己,与当前值A进行交换)。

  3. 确认结果:如果玩家成功坐下(CAS操作成功),他们就安全了,不需要再移动。如果有另一个玩家也试图坐同一个座位,并且先坐下了(CAS操作失败),那么没有成功的玩家需要继续寻找其他座位。

如何用CAS解决:

  • 无锁机制:玩家不需要一个中央权威来告诉他们谁可以坐下,他们通过比较和尝试坐下的机制自行解决冲突,这类似于无锁编程中的CAS操作

  • 原子性:在CAS中,比较和交换是作为一个不可分割的步骤执行的,保证了操作的原子性。在游戏中,玩家尝试坐下的行为也是原子的,要么成功坐下,要么不坐下并继续寻找下一个座位。

  • 并发控制:CAS操作可以控制多个线程对共享资源的并发访问,而在游戏中,玩家通过CAS原则来解决座位的争抢问题,避免了混乱。

 3.CAS出现的问题

CAS操作虽然高效,但也存在一些问题,如:

  • ABA问题:如果一个值从A变为B,然后又变回A,CAS检查会认为值没有变化,因为当前值与预期值都为A,这可能导致一些逻辑错误。

  • 循环等待:如果多个线程同时尝试更新同一个值,可能会导致线程循环等待,因为CAS操作可能会连续失败。

  • 只能保证一个共享变量的原子操作:CAS只能确保单个共享变量的原子操作,如果需要原子地操作多个共享变量,CAS就显得无能为力了。

为了解决这些问题,Java提供了一些额外的原子类,如AtomicStampedReference通过引入版本号来解决ABA问题)和AtomicReferenceArray用于原子地操作数组中的元素)。

CAS操作是现代多线程编程中实现线程安全和高效并发访问共享资源的重要工具。通过理解CAS的原理和使用场景,开发者可以更好地利用这一机制来构建高性能的并发程序。

4.CAS与锁的区别

1. 实现方式:

  • CAS 是一种基于硬件原子操作的无锁编程技术。它通过比较内存中的当前值和预期值来决定是否进行交换操作,从而实现对共享资源的原子更新。

  • 是一种更为传统的并发控制机制,它通常依赖于操作系统的同步原语(如互斥锁、信号量等)来保证在任一时刻只有一个线程可以访问共享资源。

2. 性能特性:

  • CAS 通常在低竞争环境下表现更好,因为它避免了操作系统切换线程的开销。然而,在高竞争环境下,CAS可能会导致大量的上下文切换和CPU循环等待,从而降低性能。

  • 可以提供更强的保证,如公平性(按照请求锁的顺序授予锁)和可重入性(一个线程可以重复请求它已经持有的锁)。但锁的使用可能会增加上下文切换的开销,尤其是在锁竞争激烈的情况下。

3. 可伸缩性:

  • CAS 在写入操作较少时更为高效,因为它们不需要操作系统介入,减少了上下文切换。但是,如果多个线程频繁地竞争同一块内存,CAS可能会导致“活锁”(即线程不断地重试,但很少或从未成功)。

  • 可以更好地处理高并发情况,因为操作系统可以介入来调度线程,避免单个线程长时间占用资源。但这也意味着在高并发下,锁可能会成为性能瓶颈。

4. 编程复杂性:

  • CAS 需要开发者自己管理复杂的状态和错误情况,如ABA问题(一个值从A变为B,再变回A,CAS检查会认为它没有变化)。这增加了编程的复杂性。

  • 提供了更简单的编程模型,因为操作系统负责管理锁的状态。开发者通常只需要关心何时获取和释放锁。

5. 死锁和饥饿:

  • CAS 不会直接导致死锁,因为它们不涉及操作系统级别的资源分配。但是,如果不正确地使用CAS,可能会导致其他问题,如活锁。

  • 如果使用不当,可能会导致死锁和饥饿。死锁发生在两个或多个线程相互等待对方释放锁,而饥饿是指某个线程长时间无法获得锁。

6.总结:

CAS和锁各有优势和局限性。CAS在低竞争环境下提供了一种高效且细粒度的控制方式,而锁则在高竞争和需要严格顺序控制的场景下更为适用。在实际应用中,选择哪种机制取决于具体的性能要求、并发模式和编程复杂性的权衡。在某些情况下,结合使用CAS和锁(例如,使用锁来保护低级别的CAS操作)可以提供更好的性能和可伸缩性。

二.JUC(Java.util.concurrent)中常见类的使用

 1.ReentrantLock

ReentrantLock 是 Java 中的一个接口,属于 java.util.concurrent.locks 包,它提供了一种锁机制,可以被用来控制多个线程对共享资源的访问。

1.ReentrantLock 的主要特点:

  1. 重入性:正如其名字所暗示的,ReentrantLock 允许同一个线程多次获取锁,但必须确保获取和释放锁的次数相同。

  2. 公平性ReentrantLock 允许设置公平性。如果设置为公平锁,则尝试获取锁的线程将按照请求的顺序获得锁;如果设置为非公平锁(默认),则可能发生线程饥饿,因为任何线程都有机会获取锁。

  3. 可中断:当一个线程在尝试获取锁时,如果锁已被其他线程持有,它可以中断等待。

  4. 超时ReentrantLock 提供了一个带超时参数的获取锁的方法,如果在超时时间内锁没有被获取,方法将返回。

  5. 条件变量ReentrantLock 可以与 Condition 对象配合使用,实现等待/通知机制

2.ReentrantLock 的基本使用 

public static void main(String[] args) {
        //这里可以设为true 就变为公平锁
        ReentrantLock locker = new ReentrantLock(true);
        locker.lock();//上锁
        try{
            // 受保护的代码
        }finally {//使用try - finally 确保一定会执行locker.unlock();
            locker.unlock();//释放锁
        }
    }

3.ReentrantLock vs synchronized  

  • 性能:在某些情况下,ReentrantLock 可以提供比 synchronized 更好的性能,尤其是在竞争不激烈的情况下。

  • 灵活性ReentrantLocksynchronized 提供了更多的灵活性,例如可以设置公平性、尝试非阻塞地获取锁、可中断等待等。

  • 实现方式synchronized 是一种关键字,其使用更简单,而 ReentrantLock 是一个类,需要显式地管理锁的获取和释放。

  • 可中断性ReentrantLock 允许尝试获取锁的线程在等待时被中断,而 synchronized 不支持这一点。

  • 条件队列ReentrantLock 可以与条件变量配合使用,提供更复杂的线程间协调,而 synchronized 需要配合 Objectwaitnotify 方法实现条件等待。

4.注意事项 

  • 使用 ReentrantLock 时,必须确保在每个 lock() 对应一个 unlock(),避免死锁。

  • 在使用 ReentrantLocklockInterruptibly() 方法时,要注意处理 InterruptedException

  • synchronized 不同,ReentrantLock 不会自动释放锁,必须显式地调用 unlock() 方法。

  • 在设计锁时,应考虑使用 ReentrantLock 还是 synchronized,根据实际场景和需求选择最合适的锁机制。

 2.原子类(AtomicXXX)

 如AtomicInteger、AtomicLong、AtomicBoolean等,它们提供了原子性的递增、递减、设置值等操作,基于CAS实现,相比synchronized效率更高。

AtomicInteger counter = new AtomicInteger(0);
int newValue = counter.incrementAndGet(); // 原子性增加并获取新值

 3.Semaphore(信号量)

1.Semaphore 的基本概念

  • 计数器Semaphore 有一个计数器,表示可用的许可(permits)数量。线程可以从信号量获取(take)或释放(release)许可。

  • 获取许可:当线程需要访问受信号量保护的资源时,它首先尝试从信号量获取一个许可。如果信号量的计数器大于零,计数器减一,线程成功获取许可。

  • 释放许可:线程访问完资源后,将释放一个许可,信号量的计数器加一。

  • 阻塞等待:如果信号量的计数器为零,尝试获取许可的线程将被阻塞,直到其他线程释放一个许可。

  • 特殊的信号量:锁其实就是一个特殊的信号量,如果信号量的取值要么为0要么为1,那么锁就是一个特殊的信号量.

2.Semaphore 的使用场景:

  1. 限制资源访问数量:例如,限制同时访问数据库连接的线程数量

  2. 控制线程数:例如,限制同时执行任务的线程数量

  3. 实现信号量模型:用于解决生产者-消费者问题

3.Semaphore 的基本使用: 

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private Semaphore semaphore = new Semaphore(3);

    public void accessResource() {
        try {
            // 尝试获取一个许可,最多等待1秒
            if (semaphore.tryAcquire(1, 1, TimeUnit.SECONDS)) {
                try {
                    // 访问资源
                    System.out.println("访问资源");
                } finally {
                    // 释放许可
                    semaphore.release(1);
                }
            } else {
                System.out.println("无法获取许可,已超时");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

 在这个例子中,我们创建了一个初始许可数量为3的信号量。accessResource 方法尝试获取一个许可,如果成功,将访问资源,然后释放许可。如果获取许可失败(超时),则输出提示信息。

4.Semaphore 的公平性 

ReentrantLock 一样,Semaphore 也有公平性和非公平性之分:

  • 非公平性(默认):不保证等待时间最长的线程会最先获得许可。

  • 公平性:保证等待时间最长的线程会最先获得许可。

可以通过构造函数参数来设置信号量的公平性。

5.注意事项:

  • 避免永久阻塞:如果所有线程都释放了许可,但没有线程尝试获取许可,信号量的计数器将不会改变,导致等待的线程永久阻塞。

  • 避免资源泄露:确保线程在访问完资源后释放许可,否则可能导致其他线程永久等待。

  • 使用 try-finally 结构:在获取许可后,应使用 try-finally 结构确保许可最终被释放

Semaphore 是一个强大的同步工具,可以用于控制对资源的访问,但需要谨慎使用,以避免潜在的死锁和资源泄露问题。 

 4.CountDownLatch

CountDownLatch 是 Java java.util.concurrent 包中的一个同步辅助类,它允许一个或多个线程等待一组其他线程完成操作CountDownLatch 通过一个计数器来实现这一功能,该计数器的初始值在创建 CountDownLatch 时设定,每当一个等待的操作完成时,计数器的值就会递减,当计数器的值递减到0时,所有等待在这个 CountDownLatch 上的线程就会被唤醒

1.CountDownLatch 的主要特点:

  1. 初始化计数器创建 CountDownLatch 时需要指定一个整数,这个整数作为内部计数器的初始值。

  2. 等待操作:调用 CountDownLatchawait() 方法的线程将会阻塞,直到 CountDownLatch 的计数器达到0。

  3. 计数递减:每当一个需要等待的操作完成时,调用 CountDownLatchcountDown() 方法,这会将计数器的值递减。

  4. 一次性CountDownLatch一次性的,计数器一旦达到0,就不能再次重置和使用。

  5. 不可中断await() 方法的等待是不可中断的,除非线程被中断时它恰好在等待 CountDownLatch

2.CountDownLatch 的基本使用: 

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int threadCount = 3;
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " started.");
                // 模拟工作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                System.out.println(Thread.currentThread().getName() + " finished work.");
                latch.countDown(); // 通知工作完成
            }).start();
        }

        latch.await(); // 等待所有线程完成
        System.out.println("All threads have completed their work.");
    }
}

在这个例子中,主线程创建了一个 CountDownLatch,其初始计数设置为3,表示主线程将等待3个工作线程完成它们的任务。每个工作线程在完成其工作后调用 countDown() 方法递减计数器。主线程调用 await() 方法阻塞,直到所有工作线程都完成了它们的任务并且 CountDownLatch 的计数器达到0,此时主线程继续执行,打印所有线程已完成工作的提示信息。 

3.注意事项:

  • 确保递减:在使用 CountDownLatch 时,确保每个对应的 countDown() 调用与初始化的计数器值相匹配,否则 CountDownLatch 可能不会按预期工作。

  • 一次性使用:由于 CountDownLatch 不能重置,一旦使用完毕,如果需要类似的同步机制,必须创建一个新的 CountDownLatch 实例。

  • 避免无限等待:如果忘记了调用 countDown() 或者计数器的初始值设置错误,await() 方法调用的线程可能会无限期地等待

  • 线程中断:虽然 await() 方法的等待不可中断,但可以在等待期间检查中断状态,并采取适当的中断处理措施。

CountDownLatch 是实现线程间协调的有用工具,特别是在需要等待多个线程或事件完成特定任务时。

4.运用场景(重点)

  1. 初始化等待:在应用程序启动时,主线程可能需要等待多个初始化操作(如数据库连接初始化、资源加载等)完成。此时,可以使用 CountDownLatch 来确保这些初始化操作在主线程继续之前被执行完毕。

  2. 并发任务执行当需要执行多个并发任务,并且需要在所有任务完成后进行汇总或进一步处理时,可以使用 CountDownLatch。每个任务在执行完毕后调用 countDown() 方法,而主线程则在所有任务启动后调用 await() 方法等待它们全部完成。

  3. 模拟并发:在测试或某些特定场景下,可能需要模拟多个线程同时开始执行的操作。可以通过创建一个 CountDownLatch 并初始化为 1,然后在每个线程中调用 await() 方法,主线程调用 countDown() 后所有等待的线程将被唤醒。

  4. 多线程数据汇总在多线程处理数据后,需要在某个线程中汇总所有线程的处理结果。此时,可以为每个处理线程设置一个 countDown() 操作,汇总线程使用 await() 等待所有计数减完。

  5. 资源依赖等待在一些复杂的业务逻辑中,后续操作可能依赖于多个前置操作的结果。这时,可以使用 CountDownLatch 来确保所有前置操作完成后才能执行后续操作

  6. 死锁检测:在并发编程中,死锁是一个常见问题。使用 CountDownLatch 可以模拟多个线程访问共享资源的场景尝试产生死锁,并通过测试来检测和解决死锁问题

  7. 控制并发线程数:在需要限制同时执行的线程数量时,可以使用 CountDownLatch 来控制。例如,只有当一定数量的线程完成它们的任务后,新的线程才会被允许开始执行

5.FutureTask 和 Callable

FutureTask Callable 都是 Java java.util.concurrent 包中用于异步执行任务和获取结果的类。它们允许你将任务提交到后台线程执行,并且可以在任务完成时获取结果

1.Callable 接口:

Callable 是一个接口,它比 Runnable 接口更灵活,因为它可以返回结果并抛出异常

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Runnable 接口相比,Callablecall() 方法可以返回一个值,并且可以抛出任何类型的异常。

2.FutureTask 类:

FutureTask 是一个实现了 RunnableFuture 接口的类,它将任务封装起来,允许你启动任务,并在任务完成时获取结果。

public class FutureTask<V> implements RunnableFuture<V>, java.io.Serializable {
    private final Callable<V> callable;
    // ... 其他字段和方法
}

FutureTask 可以与 Callable 对象一起使用,也可以与实现了 Runnable 接口的对象一起使用,只需将 Runnable 对象包装在 Callable 中即可。

3.使用示例:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class FutureTaskExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        Callable<String> callable = () -> {
            // 模拟长时间运行的任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Task completed";
        };

        FutureTask<String> futureTask = new FutureTask<>(callable);
        executorService.submit(futureTask);

        try {
            // 异步等待任务完成并获取结果
            String result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

在这个示例中,我们创建了一个 Callable 任务,它简单地休眠2秒,然后返回一个字符串。然后,我们创建了一个 FutureTask 实例,将其提交到线程池中执行。通过调用futureTask.get()我们可以异步等待任务完成并获取结果。

主要特点:

  • 异步执行FutureTask 允许你在后台线程中异步执行任务。

  • 获取结果:通过 FutureTask.get() 方法,你可以获取 Callable 任务的返回值

  • 异常处理FutureTask.get() 会抛出 ExecutionException,如果 Callablecall() 方法抛出了异常,这个异常会被包装在 ExecutionException 中。

  • 任务取消FutureTask.cancel(boolean mayInterruptIfRunning) 方法允许你取消任务。如果参数 mayInterruptIfRunning true,并且任务正在运行,那么线程的中断状态将被设置。

  • 运行状态FutureTask 提供了方法来检查任务是否已完成、取消或未启动。

注意事项:

  • 异常处理:确保正确处理 FutureTask.get() 方法可能抛出的 InterruptedExceptionExecutionException

  • 任务取消:即使任务被取消,call() 方法仍然可能被执行,具体取决于任务的实现和执行环境。

  • 线程中断:如果任务在等待 I/O 操作或其他阻塞操作时被取消,可能需要额外的逻辑来处理中断。

FutureTaskCallable 提供了一种强大的机制来执行异步任务并处理结果,它们在需要执行长时间运行的任务或并行处理多个任务时非常有用。

6.CyclicBarrier

CyclicBarrier 是 Java java.util.concurrent 包中的一个同步辅助类,它允许一组线程互相等待,直到所有线程都达到了某个公共屏障点(barrier point)时,这些线程才会继续执行。这个类对多线程之间的协调非常有用,特别是当一组线程需要等待其他线程完成操作时

1.CyclicBarrier 的主要特点:

  1. 重复使用:与 CountDownLatch 不同,CyclicBarrier 在释放等待的线程后可以重置,因此可以重复使用

  2. 屏障作用CyclicBarrier 可以指定一个 Runnable 命令,当所有线程都到达屏障时,该命令会被执行一次,然后屏障重置,等待下一次线程集合。

  3. 等待超时:线程在等待 CyclicBarrier 时可以指定超时时间,如果在超时时间内其他线程没有到达屏障,等待的线程会被唤醒。

  4. 中断等待:如果等待 CyclicBarrier 的线程被中断,那么这个线程会抛出 InterruptedException,并且屏障将被重置。

2.CyclicBarrier 的基本使用:

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.BrokenBarrierException;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int workerCount = 3;
        final CyclicBarrier barrier = new CyclicBarrier(workerCount, 
            () -> System.out.println("All workers have completed their task."));

        for (int i = 0; i < workerCount; i++) {
            new Thread(() -> {
                System.out.println("Worker " + Thread.currentThread().getName() + " started.");
                doWork();
                try {
                    barrier.await(); // 等待其他工人
                } catch (InterruptedException | BrokenBarrierException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }

    private static void doWork() {
        try {
            // 模拟工作
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

 在这个例子中,我们创建了一个 CyclicBarrier,它等待3个工作线程完成工作。每个线程在开始工作后调用 barrier.await() 方法,等待其他线程完成工作。当所有线程都到达屏障时,提供的 Runnable 命令被执行,屏障重置,所有线程继续执行。

3.注意事项:

  • 屏障破坏:如果在等待期间线程中断或超时,屏障将被破坏,并且 BrokenBarrierException 将被抛出。

  • 资源清理:如果 CyclicBarrier 被破坏,应该避免再次使用它,或者在捕获 BrokenBarrierException重置它。

  • 公平性CyclicBarrier 可以设置为公平或非公平模式,公平模式下将优先让等待时间最长的线程获取锁。

  • 超时:可以为 await() 方法设置超时时间,如果在超时时间内所有线程没有都到达屏障,TimeoutException 将被抛出。

CyclicBarrier 协调多个线程执行的有用工具,特别是在需要周期性地让一组线程相互等待的场景中。通过合理使用 CyclicBarrier,可以简化多线程程序的逻辑,提高程序的可读性和健壮性。

三.ThreadLocal的底层实现

加上这一个的底层实现,主要是因为博主再看到一个面经时,看到了这一个问题所以在这里记录一下

 ThreadLocal 是 Java 提供的一个线程局部变量类,它允许线程独立持有一个共享对象的局部副本。每个线程的 ThreadLocal 实例会持有一个线程局部变量的副本,这个副本仅对该线程可见,从而避免了线程间共享状态时的同步问题

1.ThreadLocal 的底层实现原理:

  1. 线程局部变量存储:每个线程 Thread 对象都有一个 ThreadLocalMap 类型的内部类,用于存储与该线程关联的 ThreadLocal 对象及其值

  2. 键值对存储ThreadLocalMap 是一个简单的键值对集合,其中键是 ThreadLocal 实例本身(通过 ThreadLocal ThreadLocalMap 内部类实现的弱引用),值是线程局部变量的副本

  3. 弱引用键ThreadLocalMap 中的键是 ThreadLocal 对象的弱引用,这意味着如果没有任何强引用指向 ThreadLocal 实例,该 ThreadLocal 实例将被垃圾回收,但它关联的 ThreadLocalMap 条目不会自动清除,可能导致内存泄漏

  4. 访问局部变量:当线程访问 ThreadLocal 提供的变量时,实际上是通过查询其内部的 ThreadLocalMap 来获取与当前 ThreadLocal 关联的值。

  5. 初始值获取:使用 ThreadLocal get() 方法时,如果没有为当前线程初始化过值ThreadLocal 会调用其 initialValue() 方法来提供一个初始值。

  6. 设置局部变量:使用 ThreadLocal set(T value) 方法时,会将当前 ThreadLocal 对象作为键,传入的值作为值,存入当前线程的 ThreadLocalMap 中。

  7. 移除局部变量ThreadLocalremove() 方法可以移除当前线程的 ThreadLocalMap 中与当前 ThreadLocal 对象关联的条目,有助于避免内存泄漏

2.示例代码: 

public class ThreadLocalExample {
    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0; // 提供一个初始值
        }
    };

    public static void main(String[] args) {
        new Thread(() -> {
            threadLocal.set(1); // 为当前线程设置值
            System.out.println(threadLocal.get()); // 输出 1
        }).start();

        new Thread(() -> {
            threadLocal.set(2);
            System.out.println(threadLocal.get()); // 输出 2
        }).start();
    }
}

在这个例子中,每个线程通过 set() 方法为 ThreadLocal 设置了一个值,并通过 get() 方法获取了属于自己的局部变量值。由于 ThreadLocal每个线程都有变量副本,所以两个线程的输出值是独立的。 

3.注意事项:

  • 内存泄漏:由于 ThreadLocalMap 的键是 ThreadLocal 实例的弱引用,如果没有外部强引用指向 ThreadLocal 对象,当 ThreadLocal 对象被垃圾回收后,ThreadLocalMap 中的条目不会自动清除,这可能导致内存泄漏。

  • 线程结束时的清理:线程结束后,其关联的 ThreadLocalMap 应该被清理,但实际上并不会自动清理。因此,应当在线程结束时或不再需要时使用 remove() 方法手动清理

  • 初始值提供ThreadLocal 提供了 initialValue() 方法,允许子类重写此方法来提供一个初始值。

通过理解 ThreadLocal 的底层实现,可以更好地利用这一特性来处理线程局部变量,同时避免潜在的内存泄漏问题。

感谢你的阅读,祝你一天愉快

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/577708.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

11.JAVAEE之网络原理1

1.应用层(和程序员接触最密切) 应用程序 在应用层这里,很多时候, 都是程序员"自定义"应用层协议的,(当然,也是有一些现成的应用层协议)&#xff08;这里的自定义协议,其实是非常简单的~~协议 >约定,程序员在代码中规定好,数据如何进行传输) 1.根据需求, 明确要传…

了解HTTP代理服务器:优势、分类及应用实践

在我们日常的网络使用中&#xff0c;我们经常听到HTTP代理服务器这个术语。那么&#xff0c;HTTP代理服务器到底是什么&#xff1f;它有什么优势和分类&#xff1f;又如何应用于实践中呢&#xff1f;让我们一起来了解一下。 HTTP代理服务器是一种位于客户端和服务器之间的中间…

中电金信:向“新”而行——探索融合架构的项目管理在保险行业的应用

近年来&#xff0c;险企在政策推动、市场牵引、自身发展、新技术应用日趋成熟等内外部因素的驱动下&#xff0c;积极投身到数字化转型的浪潮中。在拜访各类保险客户和合作项目的过程中&#xff0c;我们发现不少险企在数字化转型中或多或少都面临着战略如何落地、技术如何承接和…

美国洛杉矶站群服务器如何提高网站排名?

美国洛杉矶站群服务器怎么样?美国洛杉矶站群服务器如何提高网站排名?Rak部落小编为您整理发布美国洛杉矶站群服务器如何提高网站排名? 美国洛杉矶站群服务器可以通过以下几种方式帮助提高网站排名&#xff1a; - **提升网站性能**&#xff1a;美国站群服务器通常配备高速CPU…

python-pytorch官方示例Generating Names with a Character-Level RNN的部分理解0.5.03

pytorch官方示例Generating Names with a Character-Level RNN的部分理解 模型结构功能关键技术模型输入模型输出预测实现 模型结构 功能 输入一个类别名和一个英文字符&#xff0c;就可以自动生成这个类别&#xff0c;且以英文字符开始的姓名 关键技术 将字符进行one-hot编…

抖音小店怎么做?新店铺起店就做这3步,核心玩法来了

大家好&#xff0c;我是电商笨笨熊 做抖音小店迟迟不起店&#xff0c;店铺一直没有销量怎么办&#xff1f; 新店铺玩家前期一定都遇到过这种烦恼&#xff0c;毫无头绪不知道该从哪入手&#xff1b; 实际上&#xff0c;想要店铺快速起店&#xff0c;只需要做对三步就够了。 作…

基于Rust的多线程 Web 服务器

构建多线程 Web 服务器 在 socket 上监听 TCP 连接解析少量的 HTTP 请求创建一个合适的 HTTP 响应使用线程池改进服务器的吞吐量优雅的停机和清理注意&#xff1a;并不是最佳实践 创建项目 ~/rust ➜ cargo new helloCreated binary (application) hello package~/rust ➜ma…

一 SSM 整合理解

SSM整合理解 一 SSM整合什么 ​ 以spring框架为基础&#xff0c;整合springmvc&#xff0c;mybatis框架&#xff0c;以更好的开发。 ​ spring管理一切组件&#xff0c;为开发更好的解耦&#xff0c;以及提供框架的组件&#xff0c;如aop&#xff0c;tx。springmvc是表述层框…

Bytebase 2.16.0 - 支持 Oracle 和 SQL Server DML 变更的事前备份

&#x1f680; 新功能 支持 Oracle 和 SQL Server DML 变更的事前备份。 支持在 SQL 编辑器中显示存储过程和函数。 支持兼容 TDSQL 的 MySQL 和 PostgreSQL 版本。 支持把数据库密码存储在 AWS Secrets Manager 和 GCP Secret Manager。 支持通过 IAM 连接到 Google Clou…

积极应对半导体测试挑战 加速科技助力行业“芯”升级

在全球半导体产业高速发展的今天&#xff0c;中国“芯”正迎来前所未有的发展机遇。AI、5G、物联网、自动驾驶、元宇宙、智慧城市等终端应用方兴未艾&#xff0c;为测试行业带来新的市场规模突破点&#xff0c;成为测试设备未来重要的增量市场。新兴领域芯片产品性能不断提升、…

如何有效的将丢失的mfc140u.dll修复,几种mfc140u.dll丢失的解决方法

当你在运行某个程序或应用程序时&#xff0c;突然遭遇到mfc140u.dll丢失的错误提示&#xff0c;这可能会对你的电脑运行产生一些不利影响。但是&#xff0c;不要担心&#xff0c;以下是一套详细的mfc140u.dll丢失的解决方法。 mfc140u.dll缺失问题的详细解决步骤 步骤1&#x…

通过一篇文章让你了解STL是什么

STL 前言一、什么是STL二、STL的版本原始版本P. J. 版本RW版本SGI版本 三、STL的六大组件四、STL的重要性试题面经 五、如何学习STL六、STL的缺陷 前言 STL&#xff08;Standard Template Library&#xff09;是C编程语言的一个标准库&#xff0c;包含了一系列模板类和函数&am…

Jmeter之Beanshell详解

一、 Beanshell概念 Beanshell: BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些语法和方法;BeanShell是一种松散类型的脚本语言(这点和JS类似);BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器,具有对象脚本语言特性…

RGB灯珠的控制-单片机通用模板

RGB灯珠的控制-单片机通用模板 一、RGB控制的原理二、RGB.c的实现三、RGB.h的实现四、color色彩空间变换以及控制渐变一、RGB控制的原理 ①通过IO发送脉冲识别0/1编码,组合24Bit的RGB数据,从而控制RGB;②每个RGB灯珠通过DIN、DOU进行级联起来;③通过HSV色彩转换成RGB从而控…

BUUCTF--web(2)

1、[HCTF 2018]admin1 打开题目后发现有注册和登录两个页面&#xff0c;因为题目提示admin&#xff0c;尝试用admin进行爆破 爆破得到密码为123 登录得到flag 2、[护网杯 2018]easy_tornado1 打开题目后有三个文件&#xff0c;分别打开查看 在url地址栏中发现包含两个参数&a…

[图解]领域驱动设计伪创新-为什么互联网是重灾区-01

0 00:00:00,840 --> 00:00:03,270 今天我们来说一下 1 00:00:03,650 --> 00:00:06,255 领域驱动设计伪创新 2 00:00:06,255 --> 00:00:08,860 为什么互联网是重灾区 3 00:00:09,500 --> 00:00:12,610 这个我们分几个视频来讲 4 00:00:15,620 --> 00:00:17,5…

FIB和RIB基础

1.思考以下的topo从数据层面和控制层面分别是如何通信的 &#xff08;1&#xff09;数据层面&#xff1b;数据包从PC1经过AR1 AR2最后到达PC2&#xff0c;这就是数据层面的通信。 &#xff08;2&#xff09;控制层面&#xff1a;PC2所在的网段192.168.2.0/24是经过AR2传递给AR…

TCN-LSTM时间卷积网络长短期记忆网络多输入多输出回归预测

文章目录 效果一览文章概述 订阅专栏只能获取一份代码部分源码参考资料 效果一览 文章概述 TCN-LSTM时间卷积网络长短期记忆网络多输入多输出回归预测 matlab2021 订阅专栏只能获取一份代码 部分源码 %------------------------------------------------------------------…

SpringBoot模块化时遇到Could not autowire. No beans of ‘xxxService‘ type found.错误

SpringBoot模块化时遇到Could not autowire. No beans of xxxService type found.错误 一、SpringBoot模块化时遇到Could not autowire. No beans of xxxService type found.错误二、解决办法一三、解决办法二 一、SpringBoot模块化时遇到Could not autowire. No beans of ‘xx…

快手面试算法真题

按照html中的标签层数遍历节点名。 例如&#xff1a;html代码如下&#xff1a;(上面的数字表示层数) <!-- 1 --><div class"div1"><!-- 2 --><span class"span1"></span><!-- 2 --><p class"p1"><…
最新文章