JUC知识解析


前言

提示:本文记录了JUC并发编程里面的几个思想跟一些类的使用,例如CAS概念、原子性原理、CountDownLatch、CyclicBarrier等的使用。

例如:本文记录了JUC并发编程里面的几个思想跟一些类的使用,例如CAS概念、原子性原理、CountDownLatch、CyclicBarrier等的使用。

一、CAS是什么?

CAS 表示 Compare And Swap,比较并交换,CAS 需要三个操作数,分别是内存位置 V、旧的预期值 A 和准备设置的新值 B。CAS 指令执行时,当且仅当 V 符合 A 时,处理器才会用B 更新 V 的值,否则它就不执行更新。但不管是否更新都会返回 V 的旧值,这些处理过程是原子操作,执行期间不会被其他线程打断。
在 JDK 5 后,Java 类库中才开始使用CAS 操作,该操作由 Unsafe 类里的 等几个方法包装提供。HotSpot 在内部对这些方法做了特殊处理,即时编译的结果是⼀条平台相关的处理器CAS 指令。Unsafe 类不是给用户程序调用的类,因此 JDK9 前只有 Java 类库可以使用CAS,譬如 juc包里的 AtomicInteger类中 等方法都使用了Unsafe 类的 CAS 操作实现。

二、CAS有什么问题

CAS 从语义上来说存在⼀个逻辑漏洞:如果 V 初次读取时是 A,并且在准备赋值时仍为 A,这依旧不能说明它没有被其他线程更改过,因为这段时间内假设它的值先改为 B 又改回 A,那么 CAS 操作就会误认为它从来没有被改变过。
这个漏洞称为 ABA 问题,juc 包提供了⼀个 AtomicStampedReference,原⼦更新带有版本号的引用类型,通过控制变量值的版本来解决 ABA 问题。大部分情况下 ABA 不会影响程序并发的正确性,如果需要解决,传统的互斥同步可能会比原⼦类更高效。

三.AtomicIntger 实现原⼦更新的原理是什么?

AtomicInteger 原子更新整形、AtomicLong原⼦更新长整型、AtomicBoolean原子更新布尔类型。以原子方式将当前的值加 1,首先在 for 死循环中取得 AtomicInteger 里存储的数值,第二步对AtomicInteger当前的值加 1 ,第三步调用compareAndSet方法进行原子更新,先检查当前数值是否等于 expect,如果等于则说明当前值没有被其他线程修改,则将值更新为 next,否则会更新失败返回 false,程序会进入for 循环重新进行compareAndSset操作。

四.JUC中重要的并发工具类

1.CountDownLatch类

  • 1.1 CountDownLatch是什么?
    CountDownLatch 是基于执行时间的同步类,允许⼀个或多个线程等待其他线程完成操作,构造方法接收⼀个 int 参数作为计数器,如果要等待 n 个点就传⼊ n。每次调用countDown方法时计数器减1,await方法阻塞当前线程直到计数器变为0,由于countDown方法可用在任何地方,所以n既可以是 n 个线程也可以是⼀个线程⾥的 n 个执行步骤。
  • 1.2 案例分析
    我们创建了3个工人(线程),每个工人执行一个任务。CountDownLatch的初始计数器值为3,表示需要3个工人完成任务后,主线程才能继续执行。
  • 1.3 代码
    代码如下(示例):
import java.util.concurrent.CountDownLatch;

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

        for (int i = 1; i <= workerCount; i++) {
            Worker worker = new Worker(i, latch);
            Thread thread = new Thread(worker);
            thread.start();
        }

        System.out.println("等待所有工人完成任务...");
        latch.await();
        System.out.println("所有工人已完成任务,继续执行主线程");
    }
}

class Worker implements Runnable {
    private int workerId;
    private CountDownLatch latch;

    public Worker(int workerId, CountDownLatch latch) {
        this.workerId = workerId;
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println("工人" + workerId + "正在执行任务...");
        try {
            // 模拟工作耗时
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("工人" + workerId + "任务执行完毕");
        latch.countDown(); // 告诉CountDownLatch任务已完成
    }
}

输出结果:

等待所有工人完成任务…
工人1正在执行任务…
工人2正在执行任务…
工人3正在执行任务…
工人2任务执行完毕
工人1任务执行完毕
工人3任务执行完毕
所有工人已完成任务,继续执行主线程

小结:通过CountDownLatch,我们可以协调多个线程的执行顺序,确保某些任务在其他任务完成后再执行,从而实现线程间的同步。场景分析:类似打王者,需要5个人才能开,这个时候少一个人都进不去游戏,这种情况就是CountDownLatch

2.CyclicBarrier类

  • 2.1 CyclicBarrier是什么?
    循环屏障是基于同步到达某个点的信号量触发机制,作用是让⼀组线程到达⼀个屏障时被阻塞,直到最后⼀个线程到达屏障才会解除。构造方法中的参数表示拦截线程数量,每个线程调用方法告诉CyclicBarrier 自己到达屏障,然后被阻塞。还支持在构造方法中传入⼀个 Runnable 任务,当线程到达屏障时会优先执行该任务。适用于多线程计算数据,最后合并计算结果的应用场景。CountDownLacth 的计数器只能用⼀次,而CyclicBarrier 的计数器可使用reset方法重置,所以CyclicBarrier 能处理更为复杂的业务场景。
  • 2.2 案例分析
  • 我们创建了3个线程(工人),每个线程执行一些准备工作。CyclicBarrier的初始计数器值为3,表示需要3个线程都准备完成后才能继续执行。
  • 2.3 代码
    代码如下(示例):
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        int threadCount = 3;
        CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
            System.out.println("所有线程都准备就绪,开始执行下一步操作");
        });

        for (int i = 1; i <= threadCount; i++) {
            Worker worker = new Worker(i, barrier);
            Thread thread = new Thread(worker);
            thread.start();
        }
    }
}

class Worker implements Runnable {
    private int workerId;
    private CyclicBarrier barrier;

    public Worker(int workerId, CyclicBarrier barrier) {
        this.workerId = workerId;
        this.barrier = barrier;
    }

    @Override
    public void run() {
        System.out.println("线程" + workerId + "正在准备...");
        try {
            // 模拟准备耗时
            Thread.sleep(2000);
            System.out.println("线程" + workerId + "准备完成,等待其他线程准备...");
            barrier.await(); // 等待其他线程
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + workerId + "继续执行后续操作");
    }
}

输出结果

线程1正在准备…
线程2正在准备…
线程3正在准备…
线程2准备完成,等待其他线程准备…
线程1准备完成,等待其他线程准备…
线程3准备完成,等待其他线程准备…
所有线程都准备就绪,开始执行下一步操作
线程1继续执行后续操作
线程2继续执行后续操作
线程3继续执行后续操作
该处使用的url网络请求的数据。

小结:通过CyclicBarrier,我们可以实现多个线程在某个状态点同步等待,并在达到条件后继续执行后续操作。常见的应用场景包括多线程计算、分布式系统中的数据分片和任务拆分等。

3.Semaphore类

  • 3.1 Semaphore是什么?
    *信号量用来控制同时访问特定资源的线程数量,通过协调各个线程以保证合理使用公共资源。信号量可以用于流量控制,特别是公共资源有限的应用场景,比如数据库连接。
    Semaphore 的构造方法参数接收⼀个 int 值,表示可用的许可数量即最⼤并发数。使用方法acquire方法获得⼀个许可证,使用release方法归还许可,还可以用tryAcquire尝试获得许可。
  • 3.2 案例分析
    在这个例子中,我们创建了5个线程(工人),但是只有2个许可证(permits)可用。每个线程在访问共享资源之前都需要先获取许可证,如果没有可用的许可证,则需要等待其他线程释放许可证。
    *3.3 代码
    代码如下(示例):

public class SemaphoreExample {
    public static void main(String[] args) {
        int threadCount = 5;
        Semaphore semaphore = new Semaphore(2);

        for (int i = 1; i <= threadCount; i++) {
            Worker worker = new Worker(i, semaphore);
            Thread thread = new Thread(worker);
            thread.start();
        }
    }
}

class Worker implements Runnable {
    private int workerId;
    private Semaphore semaphore;

    public Worker(int workerId, Semaphore semaphore) {
        this.workerId = workerId;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            System.out.println("线程" + workerId + "正在尝试获取许可证...");
            semaphore.acquire();
            System.out.println("线程" + workerId + "成功获取许可证,开始访问共享资源");
            // 模拟访问共享资源
            Thread.sleep(2000);
            System.out.println("线程" + workerId + "访问共享资源结束,释放许可证");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果

线程1正在尝试获取许可证…
线程1成功获取许可证,开始访问共享资源
线程2正在尝试获取许可证…
线程2成功获取许可证,开始访问共享资源
线程3正在尝试获取许可证…
线程4正在尝试获取许可证…
线程3成功获取许可证,开始访问共享资源
线程2访问共享资源结束,释放许可证
线程5正在尝试获取许可证…
线程4成功获取许可证,开始访问共享资源
线程3访问共享资源结束,释放许可证
线程4访问共享资源结束,释放许可证
线程1访问共享资源结束,释放许可证
线程5成功获取许可证,开始访问共享资源
线程5访问共享资源结束,释放许可证

小结:通过Semaphore,我们可以限制同时访问共享资源的线程数量,保证线程安全性,避免资源竞争。使用场景包括数据库连接池、线程池、有限资源的并发访问等。

4.Exchanger类

  • 4.1 Exchanger 是什么?
    交换者是用于线程间协作的⼯具类,用于进行线程间的数据交换。它提供⼀个同步点,在这个同步点两个线程可以交换彼此的数据。
    两个线程通过exchange方法交换数据,第⼀个线程执行exchange方法后会阻塞等待第⼆个线程执行该方法,当两个线程都到达同步点时这两个线程就可以交换数据,将本线程⽣产出的数据传递给对方。
  • 4.2 案例分析
    在这个例子中,有一个生产者线程和一个消费者线程,它们通过Exchanger交换数据。生产者线程先发送数据,然后等待消费者线程提供数据;消费者线程先发送数据,然后等待生产者线程提供数据。当两个线程都调用exchange()方法后,它们会交换彼此的数据并继续执行。
  • 代码
    代码如下(示例):

public class ExchangerExample {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();

        Thread producer = new Thread(() -> {
            try {
                String data = "Hello from producer";
                System.out.println("Producer is sending data: " + data);
                String receivedData = exchanger.exchange(data);
                System.out.println("Producer received data: " + receivedData);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                String data = "Hello from consumer";
                System.out.println("Consumer is sending data: " + data);
                String receivedData = exchanger.exchange(data);
                System.out.println("Consumer received data: " + receivedData);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

输出结果:

Producer is sending data: Hello from producer
Consumer is sending data: Hello from consumer
Consumer received data: Hello from producer
Producer received data: Hello from consumer

小结:通过Exchanger,生产者和消费者线程可以进行数据的有序交换,实现线程间的协作和同步。适用场景包括数据传递、任务协作等。


总结

提示:这里对文章进行总结:
例如:以上就是我这次JUC知识点的分享了,我从概念在、原理触发,再到举例说明,再给出代码几个步骤来阐述了这次知识点。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值