Java处理并发编程工具集合(JUC)详解 1

简介: Java处理并发编程工具集合(JUC)详解

1 线程池

相关文章防止冗余:

Java由浅入深理解线程池设计和原理:https://blog.csdn.net/ZGL_cyy/article/details/133208026

Java线程池ExecutorService:https://blog.csdn.net/ZGL_cyy/article/details/117843472

Java并发计算判断线程池中的线程是否全部执行完毕:https://blog.csdn.net/ZGL_cyy/article/details/127599554

Java线程池创建方式和应用场景:https://blog.csdn.net/ZGL_cyy/article/details/126443994

2 Fork/Join

2.1 概念

  ForkJoinPool是由JDK1.7后提供多线程并行执行任务的框架。可以理解为一种特殊的线程池。
  1.任务分割:Fork(分岔),先把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割。

2.合并结果:join,分割后的子任务被多个线程执行后,再合并结果,得到最终的完整输出。

2.2 组成

  • ForkJoinTask:主要提供fork和join两个方法用于任务拆分与合并;一般用子类 RecursiveAction(无返回值的任务)和RecursiveTask(需要返回值)来实现compute方法。

  • ForkJoinPool:调度ForkJoinTask的线程池;


  • ForkJoinWorkerThread:Thread的子类,存放于线程池中的工作线程(Worker);

  • WorkQueue:任务队列,用于保存任务;

2.3 基本使用

一个典型的例子:计算1-1000的和

package com.oldlu.thread;
import java.util.concurrent.*;
public class SumTask {
    private static final Integer MAX = 100;
    static class SubTask extends RecursiveTask<Integer> {
        // 子任务开始计算的值
        private Integer start;
        // 子任务结束计算的值
        private Integer end;
        public SubTask(Integer start , Integer end) {
            this.start = start;
            this.end = end;
        }
        @Override
        protected Integer compute() {
            if(end - start < MAX) {
                //小于边界,开始计算
                System.out.println("start = " + start + ";end = " + end);
                Integer totalValue = 0;
                for(int index = this.start ; index <= this.end  ; index++) {
                    totalValue += index;
                }
                return totalValue;
            }else {
                //否则,中间劈开继续拆分
                SubTask subTask1 = new SubTask(start, (start + end) / 2);
                subTask1.fork();
                SubTask subTask2 = new SubTask((start + end) / 2 + 1 , end);
                subTask2.fork();
                return subTask1.join() + subTask2.join();
            }
        }
    }
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        Future<Integer> taskFuture =  pool.submit(new SubTask(1,1000));
        try {
            Integer result = taskFuture.get();
            System.out.println("result = " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace(System.out);
        }
    }
}

2.4 设计思想

  • 普通线程池内部有两个重要集合:工作线程集合(普通线程),和任务队列。
  • ForkJoinPool也类似,线程集合里放的是特殊线程ForkJoinWorkerThread,任务队列里放的是特殊任务ForkJoinTask

不同之处在于,普通线程池只有一个队列。而ForkJoinPool的工作线程ForkJoinWorkerThread每个线程内都绑定一个双端队列。


在fork的时候,也就是任务拆分,将拆分的task会被当前线程放到自己的队列中。


如果有任务,那么线程优先从自己的队列里取任务执行,以LIFO先进后出方式从队尾获取任务,


当自己队列中执行完后,工作线程会跑到其他队列以work−stealing窃取,窃取方式为FIFO先进先出,减少竞争。

2.5 注意点

使用ForkJoin将相同的计算任务通过多线程执行。但是在使用中需要注意:

  • 注意任务切分的粒度,也就是fork的界限。并非越小越好
  • 判断要不要使用ForkJoin。任务量不是太大的话,串行可能优于并行。因为多线程会涉及到上下文的切换

3 原子操作

3.1 概念

原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为"不可被中断的一个或一系列操作" 。

3.2 CAS

CAS(Compare-and-Swap/Exchange),即比较并替换,是一种实现并发常用到的技术。CAS的整体架构如下:

6568e04dd21847059f67fe06f24fa4da.png

  juc中提供了Atomic开头的类,基于cas实现原子性操作,最基本的应用就是计数器
package com.oldlu;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
    private static AtomicInteger i = new AtomicInteger(0);
    public int get(){
        return i.get();
    }
    public void inc(){
        i.incrementAndGet();
    }
    public static void main(String[] args) throws InterruptedException {
        final AtomicCounter counter = new AtomicCounter();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    counter.inc();
                }
            }).start();
        }
        Thread.sleep(3000);
        //可以正确输出10
        System.out.println(counter.i.get());
    }
}

注:AtomicInteger源码。基于unsafe类cas思想实现,性能篇会讲到


CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。


1.自旋(循环)时间长开销很大,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销,注意这里的自旋是在用户态/SDK 层面实现的。

2.只能保证一个共享变量的原子操作,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

3.ABA问题,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比CAS更高效。

3.3 atomic

上面展示了AtomicInteger,关于atomic包,还有很多其他类型:
  • 基本类型
  • AtomicBoolean:以原子更新的方式更新boolean;
  • AtomicInteger:以原子更新的方式更新Integer;
  • AtomicLong:以原子更新的方式更新Long;
  • 引用类型
  • AtomicReference : 原子更新引用类型
  • AtomicReferenceFieldUpdater :原子更新引用类型的字段
  • AtomicMarkableReference : 原子更新带有标志位的引用类型
  • 数组
  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。
  • 字段
  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。

3.4 注意!

使用atomic要注意原子性的边界,把握不好会起不到应有的效果,原子性被破坏。

案例:原子性被破坏现象

package com.oldlu;
import java.util.concurrent.atomic.AtomicInteger;
public class BadAtomic {
    AtomicInteger i = new AtomicInteger(0);
    static int j=0;
    public void badInc(){
        int k = i.incrementAndGet();
        try {
            Thread.sleep(new Random().nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        j=k;
    }
    public static void main(String[] args) throws InterruptedException {
        BadAtomic atomic = new BadAtomic();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                atomic.badInc();
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(atomic.j);
    }
}

结果分析:

  • 每次都不一样,总之不是10
  • i是原子性的,没问题。但是再赋值,变成了两部操作,原子性被打破
  • 在badInc上加synchronized,问题解决


目录
相关文章
|
1月前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
69 9
|
18天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
21 0
|
25天前
|
SQL Java 索引
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
137 83
|
22天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
40 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
25天前
|
Java 数据库
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
53 26
|
11天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
32 5
|
26天前
|
Java
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
49 24
|
23天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
91 6
|
23天前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
34 4
|
25天前
|
数据采集 存储 监控
Java爬虫:数据采集的强大工具
在数据驱动的时代,Java爬虫技术凭借其强大的功能和灵活性,成为企业获取市场信息、用户行为及竞争情报的关键工具。本文详细介绍了Java爬虫的工作原理、应用场景、构建方法及其重要性,强调了在合法合规的前提下,如何有效利用Java爬虫技术为企业决策提供支持。