【Java】JUC(java.util.concurrent)工具包中的并发相关

JUC指的是java.util中的并发编程工具包java.util.concurrent

主要包括以下四类:

  • Lock框架和Tools类(AQS,实现如ReentrantLock,工具类)
  • Collections并发集合(ConcurrentHashMap,CopyOnWriteList)
  • Atomic原子类(CAS,Unsafe类,原子类)
  • Executors线程池

一、AQS详解

AQS 的全称为 AbstractQueuedSynchronizerAQS 就是一个抽象类,主要用来构建锁和同步器

AQS原理

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中

在这里插入图片描述

AQS 对资源的共享方式

一共两种资源共享方式

  • 独占:只有一个线程能执行,如 ReentrantLock
  • 共享:多个线程可同时执行,如 Semaphore/CountDownLatch

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可

AQS 底层使用了模板方法模式

AQS 使用了模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的钩子方法:

protected boolean tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected int tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected boolean tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。

AQS具体应用举例

ReentrantLock

资源为独占型

  • state 初始化为 0,表示未锁定状态

  • A 线程 lock() 时,会调用 tryAcquire() 独占该锁并将 state+1 。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock() 到 state=0(即释放锁)为止,其它线程才有机会获取该锁

  • 当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。

CountDownLatch

资源为共享型

  • 任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)
  • 这 N 个子线程是并行执行的,每个子线程执行完后 countDown() 一次,state 会 CAS(Compare and Swap) 减 1
  • 等到所有子线程都执行完后(即 state=0 ),会 unpark() 主调用线程,然后主调用线程就会从 await() 函数返回,继续后余动作

二、ConcurrentHashMap,CopyOnWriteList

详细参考【Java】容器 ,内有对ConcurrentHashMap,CopyOnWriteList的详细解读

三、Atomic原子类(CAS,Unsafe类,原子类)

原子类

原子类说简单点就是具有原子/原子操作特征的类,以AtomicInteger 类为例说明原子类的特点

AtomicInteger 类主要利用CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升

CAS 的原理:拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个 volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值

通过一个简单例子带大家看一下基本数据类型原子类的优势

  1. 多线程环境不使用原子类保证线程安全(基本数据类型)
class Test {
        private volatile int count = 0;
        //若要线程安全执行执行count++,需要加锁
        public synchronized void increment() {
                  count++;
        }

        public int getCount() {
                  return count;
        }
}

  1. 多线程环境使用原子类保证线程安全(基本数据类型)
class Test2 {
        private AtomicInteger count = new AtomicInteger();

        public void increment() {
                  count.incrementAndGet();
        }
      //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。
       public int getCount() {
                return count.get();
        }
}

Unsafe类

Unsafe 是位于 sun.misc 包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等

这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。但由于 Unsafe 类使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用 Unsafe 类会使得程序出错的概率变大,使得 Java 这种安全的语言变得不再“安全”,因此对 Unsafe 的使用一定要慎重

CAS操作

CAS是Unsafe提供的方法

CAS 即比较并替换(Compare And Swap),是实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。CAS 是一条 CPU 的原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题

在 JUC 包的并发工具类中大量地使用了 CAS 操作,其作为乐观锁在并发工具类中广泛发挥了作用。在 Unsafe 类中,提供了compareAndSwapObject、compareAndSwapInt、compareAndSwapLong方法来实现的对Object、int、long类型的 CAS 操作

四、线程池Executor

Executor 框架结构(主要由三大部分组成)

  1. 任务(Runnable /Callable)

    执行任务需要实现的 Runnable 接口 或 Callable接口

  2. 任务的执行(Executor)

    ExecutorService 接口继承了Executor ,ThreadPoolExecutorScheduledThreadPoolExecutor 这两个关键类实现了ExecutorService接口

在这里插入图片描述

  1. 异步计算的结果(Future)

    Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。

    当我们把 Runnable接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。(调用 submit() 方法时会返回一个 FutureTask 对象)

Executor 框架的使用示意图

在这里插入图片描述

  1. 主线程首先要创建实现 Runnable 或者 Callable 接口的任务对象
  2. 把创建完成的实现 Runnable/Callable接口的 对象直接交给 ExecutorService 执行: ExecutorService.execute(Runnable command))或者也可以把 Runnable 对象或Callable 对象提交给 ExecutorService 执行(ExecutorService.submit(Runnable task)或 ExecutorService.submit(Callable task))。
  3. 如果执行 ExecutorService.submit(…),ExecutorService 将返回一个实现Future接口的对象(我们刚刚也提到过了执行 execute()方法和 submit()方法的区别,submit()会返回一个 FutureTask 对象)。由于 FutureTask 实现了 Runnable,我们也可以创建 FutureTask,然后直接交给 ExecutorService 执行。
  4. 最后,主线程可以执行 FutureTask.get()方法来等待任务执行完成。主线程也可以执行 FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

几种常见的线程池

  1. 创建线程池可以通过new ThreadPoolExecutor(...)自己指定参数获取线程池
  2. 也可以通过Executors 类的静态方法创建几种常见的线程池,这种方式本质上是用了固定参数new ThreadPoolExecutor(...)返回的线程池
    • newFixedThreadPool:固定线程数的线程池
    • SingleThreadExecutor:只有一个线程的线程池
    • CachedThreadPool:根据需要创建新线程的线程池,最大创建Integer.MAX_VALUE个
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值