【一图流思维导图】Java JUC并发编程 案例展示步骤(售票,2个线程对一个变量+1、-1,3个线程按顺序打印字符,6个同学离开教室后班长锁门,收集七龙珠,停车场3个车位6辆车停放,银行弹性窗口)

请添加图片描述

Java JUC并发编程

JUC简介

概念区分

  • JUC是java.util.concurrent包的简称

    • 更好的支持高并发任务
    • 让开发者进行多线程编程时减少竞争条件和死锁的问题
  • 进程

    • 线程
  • 串行

  • 并行

    • 一边烧水 一边切菜
  • 并发

    • 同一资源争夺

      • 春运抢票
      • 电商秒杀
  • 管程 Monitor 监视器

    • 同步机制 保证同一时间,只有一个线程访问被保护的数据或者代码
  • Thread.sleep 与 Object.wait 区别

  • 用户线程

    • 守护线程

Lock接口

加锁方式

  • synchronized

    • 同步锁

      • 自动加锁 释放锁
  • ReentryLock

    • 可重入锁

      • 手动lock() try{} finnally unlock()

创建线程的多种方法

  • 继承 Thread

  • Runnable 接口

  • Callable 接口

    • FutureTask
  • 线程池

    • Executors
    • ThreadPoolExecutor

案例:售票员卖票

  • 方式

    • 30张票 3个售票员(线程)来卖票
  • 分析

    • 资源类 Ticket

      • tnum

        • 总票数
      • 两种方式对应的方法

        • synchronized 方式1

          • sell() 方法

            • synchronized sell()
        • 可重入锁方式2

          • sell() 方法

            • 手动 lock try{} finally{unlock}
    • 创建3个线程

      • 每个线程循环卖票30次

多线程编程步骤(上)

  • 1 创建资源类,创建属性和方法
  • 2 创建多线程,调用资源的方法

线程间通信 定制通信

案例:两个线程,对一个初始值是0的变量,一个线程+1,一个线程减1

  • 分析:

    • 创建资源Share

      • num

        • 变量
      • 可重入锁方式

        • ReentrantLock

        • Condition

          • con
      • incr()方法负责 +1

      • decr()方法负责 -1

    • 创建两个线程

      • 1个线程 负责不停调用incr()
      • 1个线程 负责不停调用decr()
      • 两个线程需要来回等,需要通信
    • 两个线程需要依据num的当前状态,临时中断,等待对方线程执行

    • 这个时候就需要 判断

      • 条件满足

        • 自己执行
        • 等待wait
  • synchronized 方式

    • incr()方法中判断 num

      • 如果不为 0

        • this.wait();
      • 如果为0

        • 执行+1,再通知其他线程 this.notifyAll();
  • ReentrantLock 可重入锁方式

    • 创建 lock.newCondition()

    • incr()方法中判断 num

      • 如果不为 0

        • condition.await();
      • 如果为0

        • 执行+1,再通知其他线程 condition.signalAll();

多线程编程步骤(中 下)

  • 1 创建资源类,创建属性和方法

  • 2 在资源类操作方法中

    • 1 判断

      • wait 在哪里等待 在哪里唤醒

        • 注意虚假唤醒
        • 判断条件 if->while
    • 2干活

      • do something
    • 3通知

      • noticeAll()
  • 3 创建多线程,调用资源的方法

案例:三个线程(AA,BB,CC) 按照如下顺序打印:

AA打印5次,BB打印10次,CC打印15次
总共10轮

  • 分析:

    • 创建资源 ShareRes

      • flag

        • 依据flag的1,2,3 标识线程的先后顺序
      • lock

        • ReentrantLock
      • Condition

        • con1
        • con2
        • con3
      • print5()

      • print10()

      • print15()

    • 创建3个线程

      • AA线程调用 print5()

        • 判断flag是否为1

          • flag!=1

            • con1.await()
          • flag=1

            • 执行
            • 修改flag=2
            • 定制 通知BB con2.await()
      • BB线程调用 print10()

        • 判断flag是否为2

          • flag!=2

            • con2.await()
          • flag=2

            • 执行
            • 修改flag=3
            • 定制 通知CC con3.await()
      • CC线程调用 print15()

        • 判断flag是否为3

          • flag!=3

            • con3.await()
          • flag=3

            • 执行
            • 修改flag=1
            • 定制 通知AA con1.await()
      • 3个线程中,需要定制通信,指定唤醒的线程

集合的线程安全

集合ArrayList

  • 线程不安全演示

    • 创建40个线程,分别向集合中add() 再遍历
    • 报出异常 ConcurrentModificationException
  • 解决方法

    • Vector

      • synchronized
    • Collections.synchronizedCollection

    • CopyOnWriteArrayList

      • 写时复制技术

        • 分析

          • 并发读
          • 有更新时,复制一份新的副本,更新
          • 其他线程从老的副本读
          • 更新完成,所有线程从新的副本读
        • 类比

          • 签到名单
      • Happen-before原则

HashSet

  • 解决方法

    • Collections.synchronizedCollection
    • CopyOnWriteArraySet

HashMap

  • 解决方法

    • HashTable

      • synchronized
    • ConcurrentHashMap

      • 1.7

        • Segment 是 ConcurrentHashMap 的一个内部类
        • class Segment<K,V> extends ReentrantLock
        • 核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。
        • 采用了分段锁技术
      • 1.8

        • 抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性

        • CAS compare and swap 比较并交换

          • 内存位置(V)、预期原值(A)和新值(B)
          • 如果内存位置的值与预期原值相匹配,
            那么处理器会自动将该位置值更新为新值 。
            否则,处理器不做任何操作
          • 我认为位置 V 应该包含值 A;
            如果包含该值,则将 B 放到这个位置;
            否则,不要更改该位置,只告诉我这个位置现在的值即可。

多线程锁

synchronized 锁的3中形式

  • 普通同步方法

    • 当前对象 this
  • static 同步方法

    • 类的class对象
  • 同步方法块

    • 括号中的对象

公平锁 fair,非公平锁 nofair

  • 演示

    • new ReentrantLock(true);
    • new ReentrantLock(false);
  • 公平锁

    • 阳光普照
    • 效率稍微低
  • 非公平锁

    • 线程饿死
    • 效率高

可重入锁 递归锁

  • synchronized (隐式) Lock (显式) 都是可重入锁

死锁

  • 线程A B 争夺资源时,相互等待的情况

  • 原因

    • 系统资源不足
    • 推进顺序不合适
    • 资源分配不当
  • 查看程序等待是不是死锁

    • jps
    • jstack pid

Callable接口

Callable call()方法有返回值 ,可以引发异常

Runnable

  • FutureTask构造可以传递Callable

步骤演示

  • FutureTask(Callable)
  • new Thread(futureTask,“A”).start();
  • futureTask.isDone()
  • futureTask.get()

FutureTask 未来任务原理

  • 老师上课,口渴了,需要喝水。安排班长买水 等班长买回来,老师就能喝水了

  • 考试时,先做会的题目,再做不会的题目

  • 汇总一次

    • 下次调用get ,不需要重新执行一遍

辅助工具类

减少计数器CountDownLatch

  • 可以多个线程调用 await()来等待

    • 计数器为0 ,这些线程都会被唤醒
  • 案例:6个同学依次离开教室,最终班长锁门

    • 分析

      • 定义一个计数器,一开始6
      • 每个同学离开后,计数器减1
      • 班长一直在等,直到计数器变成0,锁门
    • 实现步骤

      • new CountDownLatch(6)

      • 创建6个线程,6个同学离开,每个线程里面

        • countDownLatch.countDown();
      • main线程 班长等待

        • countDownLatch.await();
      • 计数器变成0,班长 离开教室

循环栅栏 CyclicBarrier

  • 循环柱塞 构造一个目标障碍数

    • 障碍慢慢被清除,目标可以到达
  • 案例,集齐七龙珠,召唤神龙

    • 步骤

      • new CyclicBarrier(NUMBER,Runnable)

      • 创建7个线程分别收集龙珠,每个线程里面

        • cyclicBarrier.await();
      • 等7个线程都执行完,Runnable 里面召唤神龙

信号量 Semaphore

  • 维护了一个许可集,可以限制访问某些资源的线程数量

  • 案例:停车场 3个车位,6辆车准备停车

    • 步骤

      • new Semaphore(3) permits

      • 创建6个线程,模拟停车,每个线程

        • 申请许可 抢占

          • semaphore.acquire();
        • 设置停放时间

        • 时间到后,释放许可

          • semaphore.release();
      • 这样6两车都能找到车位

ReadWriteLock读写锁

乐观锁 悲观锁

读锁

  • 共享锁

写锁

  • 独占锁

读写锁的演变

  • 第一阶段 无锁

    • 容易出现 并发修改异常
  • 第二阶段 独占锁

    • 读读 读写 写写 都不能共享
  • 第三个阶段 读锁 写锁

    • 优点

      • 读读 可以共享
    • 缺点

      • 造成锁饥饿,一直读,没有写操作

        • 读的时候 不能写,等结束 才能写
    • 只有读完成之后,才可以写,写操作可以读

锁的降级

  • 获取写锁

    • 获取读锁

      • 释放写锁

        • 释放读锁

演示

  • 定义资源 MyCache

    • 属性

      • volatile map
      • ReentrantReadWriteLock
    • 方法

      • put(k,v)

        • 获得写锁 rwLock.writeLock().lock();
        • 模拟业务处理,等待300ms
        • map.put(k,v)
      • get(k)

        • 获得读锁 rwLock.readLock().lock();
        • 模拟业务处理,等待300ms
        • return map.get(k)
  • 开启多线程

    • 创建5个线程 通过put 添加数据
    • 等待300ms
    • 创建5个线程,通过get读取数据
  • 结果

    • 5个线程依次写
    • 写完之后,5个线程同时读

BlockQueue阻塞队列

理解

  • 首先是一个队列,通过一个共享的队列,使得数据从队列的一段流入,从另外一端流出

  • Thread1 .put

    • BlockingQueue

      • Thread2.take

好处

  • 我们不要关系什么时候需要阻塞线程,什么时候需要唤醒

    • 添加时,如果队列满了,会自动阻塞
    • 取出时,如果队列为空,自动阻塞

应用

  • 有若干生产者,有若干消费者,通过队列的方式来传递,就很方便

    • 之前需要考虑生产速度是不是能够满足消费的速度

演示

  • new ArrayBlockingQueue(3)

    • capacity 队列容量为3
  • System.out.println(queue.offer(“a”));

    • 依次执行4次put方法
    • 第四个put会阻塞,因为阻塞队列的容量是3

LinkedBlockingQueue 高效并发处理数据

  • 生产端 和消费端采用的独立的锁

    • 后面的线程池就是选用的这个阻塞队列

ThreadPool线程池

框架

  • Executors

  • ThreadPoolExecutor

    • ExecutorService

      • Executor
  • ThreadFactory

演示

  • Executors.newSingleThreadExecutor()

    • 一池一线程

      • 允许队列的最大长度是Integer.MAX_VALUE,容易OOM
  • Executors.newFixedThreadPool(fixNum)

    • 一池N线程

      • 允许队列的最大长度是Integer.MAX_VALUE,容易OOM
  • Executors.newCachedThreadPool()

    • 可扩容

      • 允许创建的线程数量是Integer.MAX_VALUE,容易OOM

自定义线程池

  • new ThreadPoolExecutor(7个参数)

    • corePoolSize

      • 常驻线程数量
    • maximumPoolSize

      • 最大数量
    • keepAliveTime

      • 存活时间
    • unit

      • 时间单位
    • workQueue

      • BlockingQueue

        • 阻塞队列
    • Executors.defaultThreadFactory()

      • ThreadFactory

        • 线程工厂
    • defaultHandler

      • RejectedExecutionHandler

        • 拒绝策略

          • AbortPolicy

          • Discard

            • 默默丢弃,无异常,无返回
            • 如果允许丢失
          • CallerRuns

            • 调用者运行 回退到调用者
            • 降低新任务的流量
          • DiscardOldest

            • 抛弃队列中等待最久的,
              也就是最前面的

举例参照

  • 银行业务窗口,一开始3个窗口,最多可以同时开始10个窗口

ForkJoinPool分支合并

理解

  • 需要递归拆分的任务

    • 继承后,可以实现递归自己调用自己的任务
  • fork

    • 将大任务递归拆分成小任务
  • join

    • 把分拆的业务结果合并
  • ForkJoinPool

    • 分支合并池

      • 提供线程满足计算任务
  • RecursiveTask

    • 递归任务

演示

  • 创建递归任务
    MyTask extends RecursiveTask

    • 常用属性

      • VALUE=10;
      • begin
      • end
      • result
    • public MyTask(int begin,int end){}

      • 构造函数
    • 重写 Integer compute() @Override

      • 拆分成2个小任务

        • new MyTask(begin, mid)

          • task01
        • new MyTask(mid+1, end)

          • task02
      • 子任务迭代执行

        • task01.fork();
        • task02.fork();
      • 合并子任务执行结果

        • return task01.join()+task02.join();
  • 主线程

    • poo=new ForkJoinPool()

      • 创建分支合并池
    • myTask=new MyTask(1, 100)

      • 创建递归任务
    • forkJoinTask pool.submit(task)

      • 返回ForkJoinTask
    • forkJoinTask.get()

      • 阻塞等待结果
    • pool.shutdown()

      • 关闭

CompletableFuture异步回调

Future

  • 同步回调

    • isDone 判断是否完成
    • get() 阻塞到任务结束

CompletableFuture

  • 异步回调

  • 演示

    • 没有返回值

      • CompletableFuture cf1 =
        CompletableFuture.runAsync()

        • Runnable 只有run 没有返回值
      • cf1.get();

    • 有返回值

      • CompletableFuture cf2 = CompletableFuture.supplyAsync()

        • Supplier T get() 有返回值
      • cf2.whenComplete((t,u)->{}).get()

        • t 返回值
        • u 发生的异常

XMind - Trial Version

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值