likes
comments
collection
share

初识并发编程【3】之JUC

作者站长头像
站长
· 阅读数 17

本文源自Recently祝祝,创自Recently祝祝。转载请标注出处。

此解决方式在企业中有所应用,适合Java初级开发学习,参考。

本文字数与行数,耐心阅读必有收获。

初识并发编程【3】之JUC

1.JUC说明

JUC就是java的一个包java.util.concurrent的简称,java中用于并发编程常用的一个工具包,具有强大的功能,能弥补synchronized缺陷,synchronized不能够设置阻塞时长,JUC不仅仅可以设置超时时间而且还可以主动的中断线程。而且在读多写少的场合JUC可以有多种解决方式,而避免了synchronized加锁带来的开销。

JUC是解决多线程安全一个工具包,提供了非常好用的工具以及类,比如接下来要讲的原子类(Automic),锁(Lock)等解决一些不满足多线程特性的问题,提高程序性能和安全性,跟方便的管理与调度。JUC是Java并发编程不可或缺的部分。

2.JUC使用在哪里

JUC用于支持高效、安全的多线程编程。具体来说,JUC 包括线程池、原子类、锁(Lock)、并发集合、并发容器、阻塞队列

  1. 线程池:通过 Executor、ExecutorService、ThreadPoolExecutor 等类,实现线程池的创建、管理和调度,避免了线程频繁创建和销毁的开销,提高了多线程程序的效率。
  2. 原子类:Automic包,java.util.concurrent.automtic,通过 AtomicInteger(整数原子型)、AtomicLong(长整型原子类)、AtomicReference(引用类型原子类) 等类,提供线程安全的原子操作,用法简单性能高效,有效避免Volatie原子性操作变量的问题,避免了多线程并发情况下的数据竞争和安全问题。
  3. 并发集合:通过 ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentSkipListMap 等类,提供线程安全的集合操作,避免了多线程并发情况下的数据竞争和安全问题。
  4. 阻塞队列:通过 DelayQueue 类,实现基于时间的任务调度,支持在指定时间后执行任务,避免了使用 Thread.sleep() 的效率问题。
  5. 锁和条件变量:通过 Lock、Condition 等接口和实现类,提供更灵活、更高效的线程同步机制,支持更细粒度的锁控制。

初识并发编程【3】之JUC

3.JUC主要使用

3.1原子类

原子类可以提供线程安全的原子操作,避免多线程并发情况下的数据竞争和安全问题。

Aotumtic包共13个类,4种类型的原子更新方式(原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段))Atomic包里的类基本都是使用Unsafe实现的包装类。

举例:

public class VolatileNoAtomicity {
    public static void main(String[] args) throws InterruptedException {
        VolatileDemo demo = new VolatileDemo();
        for (int i = 0; i < 2; i++) {
            Thread t = new Thread(demo);
            t.start();
        }
        Thread.sleep(1000);
        System.out.println("count = "+demo.count);
    }
    static class VolatileDemo implements Runnable {
        public volatile int count;
        //public volatile AtomicInteger count = new AtomicInteger(0);
        public void run() {
            addCount();
        }
        public void addCount() {
            for (int i = 0; i < 10000; i++) {
                count++;//但是实际情况是三条汇编指令
            }
        }
    }
}

你会发现结果不是20000,因为volatile不具备原子性。

初识并发编程【3】之JUC

原因分析:

  1. 线程1读取count的值为5

  2. 线程2读取count的值为5

  3. 线程2加1操作

  4. 线程2最新count的值为6

  5. 线程2写入值到主内存的最新值为6

  6. 线程1执行加1count=6,写入到主内存的值是6。

  7. 结果:对count进行了两次加1操作,主内存实际上只是加1一次。结果为6

  8. 这样的情况出现多次之后,就会放结果与预期结果不一致。

解决使用原子操作:

初识并发编程【3】之JUC

初识并发编程【3】之JUC 你就会发现,结果变成了20000,这就是原子类的保证了原子性。

3.2什么是CAS

CAS(Compare and Swap:比较替换)一种无锁算法(乐观锁机制),可以保证在多线程并发情况下对变量的操作是原子性的。同步组件中大量使用CAS技术实现了Java多线程的并发操作。整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的,甚至ConcurrentHashMap在1.8的版本中也调整为了CAS+Synchronized。可以说CAS是整个JUC的基石。

CAS 的基本思想是比较当前内存中的值和期望值是否相等,如果相等,则执行操作并更新内存中的值,否则不执行任何操作。本质一个方法调用一行CPU原子操作执行函数:CAS(V,E,N)当且仅当内存地址V中的值等于 预期值E 时,将内存V中的值改为N,进行暂停一会儿。

3.3Lock锁

Lock 锁,锁所锁住的是一个对象显式锁机制,与 synchronized 关键字相对应,使用起来粒度更细致,并且更灵活,可以实现公平锁、非公平锁、可重入锁等,同时还可以实现多个条件变量等高级功能。可以实现异步变同步的转换。

3.4什么是AQS

AQS是一个并发基础框架类,用于构建lock、同步器等的底层框架模型。AQS是一个队列,为了让线程来排队,其中每个节点表示一个等待线程,当一个线程请求锁资源时,如果锁已被占用,该线程将被加入到队列中等待,称为线程同步器。本身并不是一种具体的同步器,而是一个提供了同步器实现所需的底层机制的抽象类。实现自定义服务器,也可以使用AQS提供的一些模板来实现同步器的基本功能。

3.5JUC工具类

线程协作工具类,控制线程协作的工具类,让线程之间的协作变得更加简单

1) CountDownLatch倒数门栓

倒数结束之前,一直处于等待状态,直到数到0结束,此线程才继续工作。

场景:购物拼团,大巴人满发车,分布式锁

主要方法:

  • 构造函数:new CountDownLatch(int count):只有一个构造函数,参数count为需要倒数的数值。
  • await():当一个或多个线程调用await()时,这些线程会阻塞。
  • countDown():其他线程调用countDown()会将计数器减1,调用countDown方法的线程不会阻 塞。当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
/**
* CountDownLatch案例:6个程序猿加班
* 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
*/
public class Demo11CountDownLatch {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try { TimeUnit.SECONDS.sleep(5); } catch
                    (InterruptedException e) {e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName() + "\t上
                                   完班,离开公司");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        new Thread(()->{
            try {
                countDownLatch.await();//卷王也是有极限的,设置超时时间
                System.out.println(Thread.currentThread().getName()+"\t卷王最
                                   后关灯走人");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "7").start();
    }
}

2) Semaphore信号量:限流

用来限制或管理数量有限资源的使用情况。 (雪崩)

场景:Hystrix、Sentinel限流

信号量的作用就是维护一个”许可证”的计数,线程可以”获取”许可证,那信号量剩余的许可证就减少一个,线程也可以”释放”一个许可证,那信号量剩余的许可证就可以加一个。当信号量拥有的许可证数为0时,下一个还要要获取许可证的线程就需要等待,直到有另外的线程释放了许可证。

主要方法: 核心方法不多

  1. 构造函数:new Semaphore(int permits,Boolean fair):可以设置是否使用公平策略,如果传入true,则 Semaphore会把之前等待的线程放到FIFO队列里,以便有了新许可证可以分给之前等待时间最长 的线程。
  2. acquire():获取许可证,当一个线程调用acquire操作时,他要么通过成功获取信号量(信号量减 1),要么一直等待下去,直到有线程释放信号量,或超时。
  3. release():释放许可证,会将信号量加1,然后唤醒等待的线程。
/**
* Semaphore案例:三辆小汽车抢车位
* Semaphore信号量主要作用:1.用于多个共享资源的互斥使用,2.用于并发线程数的控制
*6个线程抢3个车位
*/
public class Demo12Semaphore {
    public static void main(String[] args) {
        //模拟资源类,有3个空车位
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try{
                    //占有资源
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"\t
                                       抢到车位");
                    try { TimeUnit.SECONDS.sleep(3); } catch
                        (InterruptedException e) {e.printStackTrace(); }
                    System.out.println(Thread.currentThread().getName()+"\t
                                       停车3秒后离开车位");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放资源
                    semaphore.release();
                }
            }, "Thread-Car-"+String.valueOf(i)).start();
        }
    }
}

3)CyclicBarrier循环栅栏

线程会等待,直到线程到了事先规定的数目,然后触发执行条件进行下一步动作

场景:并行计算

当有大量线程互相配合,分别计算不同任务,并且需要最后统一汇总时,就可以用CyclicBarrier,它可以构造一个集结点,当某一个线程执行完,它就会到集结点等待,直到所有线程都到集结点,则该栅栏 就被撤销,所有线程统一出再,继续执行剩下的任务。

主要方法:

  • 构造函数:new CyclicBarrier(int parties, Runnable barrierAction),设置聚集的线程数量和集齐线程 数的结果之后要执行的动作。

  • await():阻塞当前线程,待凑齐线程数量之后继续执行

import java.util.concurrent.CyclicBarrier;
/**
* 案例:集齐7龙珠召唤神龙
*/
public class Demo13CyclicBarrier {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("======召唤神龙");
        });
        for (int i = 1; i <= 7; i++) {
            final int tempInt = i;
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName() +
                                       "\t收集到第" + tempInt + "颗龙珠");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName() +
                                       "\t第" + tempInt + "颗龙珠飞走了");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "Thread-"+String.valueOf(i)).start();
        }
    }
}

文章全为个人理解,如果发现部分跟你所知道的有出入,欢迎在评论区指出,欢迎探讨