JUC


什么是JUC?JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包

线程、进程和程序

进程:

进程:一个程序,程序的集合,一个进程常常包含多个线程,至少包含一个。
进程作为自愿的分配单位,每个线程拥有独立的栈和程序计数器(PC),线程切换的开销小

线程:

1、是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
2、Java默认两个线程:main和GC;
3、线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小。
4、一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简介、高效。同时也面临着共享资源带来的线程安全隐患

程序

是完成特定操作的一组指令集合,即指一段静态代码,静态对象。是进程未运行时的状态。

线程的6种状态:

public enum State {
        //新生状态
        NEW,

				//运行时
        RUNNABLE,

				//阻塞
        BLOCKED,

				//等待
        WAITING,

				//超时时间
        TIMED_WAITING,

			  //终止
        TERMINATED;
    }

Wait和Sleep的区别

1、来自不同的类

wait⇒Object

Sleep⇒Thread

2、关于锁的释放

wait会释放锁,sleep不会释放锁(sleep抱着锁睡觉?)

3、使用范围不同

wait必须在同步代码块中

sleep可以在任何地方

4、 是否需要捕获异常

wait 不需要捕获异常

sleep必须捕获异常

并发和并行


并发编程的本质就是充分的利用CPU资源!!!

并发:

并发:多线程操作同一个资源;

  • CPU一核,模拟出来多条线程(线程与线程快速交替)

并行

并行:多个人一起行走;

  • CUP多核,多个线程同时执行多个任务

Lock锁(重点)


synchronized(传统方式)

本质上是队列、锁

Lock锁

公平锁:

公平的方式,线程遵循先来后到规则

非公平锁:

不公平的,线程可以插队(默认)

Lock接口的实现类

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled.png

使用方法

Lock l = new ...; 
     l.lock(); //加锁
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock(); //解锁
     }

Synchronized和Lock的区别

  1. synchronized 内置的Java关键字,Lock是JUC包下一个Java接口
  2. synchronized 无法判断锁的状态,Lock可以判断是否获取到了锁
  3. synchronized 会自动释放锁,Lock必须要手动释放锁,如果不释放则死锁
  4. synchronized 线程1阻塞时线程2会一直等待,Lock则不一定会等。
  5. synchronized 可重入锁,不可中断的;Lock可重入锁,可以判断锁,公平性自己设置
  6. synchronized 适合锁少量的代码同步块,Lock适合锁大量的同步代码块。

Condition同步监视器

八锁现象

  • synchronized 锁的对象是方法的调用者
  • static synchronized 静态方法,锁定是Class(模板类)

集合类不安全


CopyOnWrite:写入时复制,计算机程序设计的一种优化策略;

HashSet底层是HashMap

解决List线程不安全

  • 方式一:使用Vector (底层使用synchronized)
  • 方式二:使用**Collections.synchronizedList()**的集合工具
  • 方式三:使用 **new CopyOnWriteArrayList()**类 ;JUC

解决Set线程不安全

  • 方式二:使用**Collections.synchronizedSet()**的集合工具
  • 方式三:使用 **new CopyOnWriteArraySet()**类 ;JUC

解决Map线程不安全

  • 方式:使用 new ConcurrentHashMap<>() JUC

Callable


Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。

常用的辅助类


1、CountDownLatch

减法计数器

常用方法

  • CountDownLatch:构造器方法,初始化减法计数器的值
  • countDown:从初始化的值开始,执行一次,计数器-1
  • await:等待计数器归零,然后向下执行

2、CycliBarrier

加法计数器

常用方法

  • CyclicBattier:构造器方法,初始化加法计数器的值;[可选到达初始值时是否开启一条新线程]
  • await:从1开始,执行一次,计数器+1,直到设定的值为止

3、Semaphore

信号量

常用方法

  • acquire:从该信号量获取许可证,等待直到可用为止。
  • release:释放许可证,将其返回到信号量。

ReadWriteLocak (读写锁)


ReadWriteLock:读写锁的一个接口,唯一实现类ReentrantLock;作用:更加细粒度的控制,只允许一个线程同时写入,可以允许多个线程同时进行读操作。

独占锁(写锁):一次只能被一个线程占有

共享锁(读锁):多个线程可以同时占有

阻塞队列(Blocking Queue)


FIFO(First Input First Output):即先进先出的意思

阻塞队列通常用于多线程并发处理、线程池的情况下

阻塞队列模型:

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%201.png

使用队列

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%202.png

扩展:

  • AbstractQueue:非阻塞队列
  • Deque:双端队列

阻塞队列的特殊队列-同步队列 (SynchronousQueue)


同步队列 (SynchronousQueue):没有容器量,不存储元素;放入(put)一个元素,必须取出(take)后才能继续放入新元素。放入不取则会阻塞,所以也是阻塞队列的一种。

特点:

  1. 一个插入方法的线程必须等待另一个线程调用取出。
  2. 队列没有容量Capacity(或者说容量为0),事实上队列中并不存储元素,它只是提供两个线程进行信息交换信息的场所。
  3. 队列在很多场合表现的像一个空队列。不能对元素进行迭代,不能peek元素,poll会返回null。
  4. 队列中不允许存入null元素。
  5. SynchronousQueue如同ArrayBlocking一样,支持“公平”策略。

线程池


线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。

创建线程池后需要关闭(shutdown方法)

Executors线程工具类(不推荐使用)


是对操作ThreadPoolExecutor封装的工具类,不推荐使用

Executors工具类的三大方法:

  1. Executors.newFixedThreadPool():创建固定大小的线程池
  2. Executors.newSingleThreadExecutor():创建单个大小的线程池
  3. Executors.newCachedThreadPool():动态创建大小的线程池

ThreadPoolExecutor创建线程池类


创建线程池的类

ThreadPoolExecutor线程池的七大参数:

  1. int corePoolSize:核心线程池大小
  2. int maximumPoolSize:最大核心线程池大小
  3. long keepAliveTime:当扩容线程指定时间内未使用,释放线程扩容线程
  4. TimeUnit unit:超时单位
  5. BlockingQueue<Runnable> workQueue:阻塞队列(最大等待的请求数)
  6. ThreadFactory threadFactory:线程工厂,创建线程地(通常不用改动)
  7. RejectedExecutionHandler handle:拒接策略

ThreadPoolExecutor(参数七)的四种拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy():默认,不处理并抛出异常
  2. ThreadPoolExecutor.CallerRunsPolicy():不处理并按原路返回
  3. ThreadPoolExecutor.DiscardPolicy():队列满了、丢掉任务、不会抛出异常
  4. ThreadPoolExecutor.DiscardOldestPolicy():队列满了时、尝试去和最早的竞争、不会抛出异常

扩展:CPU密集型和I/O密集型(调优)


I/O(读写密集型) :

指的是系统的cpu效能 比磁盘读写效能要高很多,这时是cpu在等io(磁盘到内存)的读写,此时cpu使用率不高;在服务器上进行网络通讯、网络传输、磁盘读写等均为IO操作,多为网络请求压力大、磁盘读写频繁的操作;io密集型的可能需要对磁盘进行升级、提高磁盘的相应速度和传输效率或通过负载技术将文件读写分散到多台服务器中;如果网络请求负载较高,可通过负载均衡技术、水平扩展提高负载。

CPU(计算密集型):

指的是系统的磁盘读写效率高于cpu效率,此时cpu效率可能达到100%,但是磁盘读写速度很快;多用来做计算、逻辑判断等cpu操作。可考虑通过消息队列或其他降维算法,将计算分散到不同的计算节点

四大函数式接口


函数式接口:即只有一个方法的接口

必须掌握的新特性:lambda表达式、链式编程、函数式接口、Stream流式计算

消费者接口:Consumer

消费性接口 void accept(T t) 有参数、没有返回值

断定(言)型接口:Predicate

boolean test(T t) 有参数、有boolean类型返回值

函数型接口:Function

R apply(T t)有参数,有返回值

供给型接口:Supplier

T get() 没有参数、有返回值

Stream流式计算


流的简短定义:从支持数据处理操作的源生成的元素序列。例如集合、数组都是支持数据操作的数据结构(容器),都可以做为流的创建源。用于对数据的处理

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%203.png

流的核心


源:

流是从一个源创建来而来,而且这个源是支持数据处理的,例如集合、数组等。

元素序列

流代表一个元素序列(流水线),因为是从根据一个数据处理源而创建得来的。

数据处理操作

流的侧重点并不在数据存储,而在于数据处理,例如示例中的filter、map、forEach等。

迭代方式

流的迭代方式为内部迭代,而集合的迭代方式为外部迭代。例如我们遍历Collection接口需要用户去做迭代,例如for-each,然后在循环体中写对应的处理代码,这叫外部迭代。相反,Stream库使用内部迭代,我们只需要对流传入对应的函数即可,表示要做什么就行。

Stream的操作步骤

创建源:

创建一个数据源,如:集合、数组

中间操作:

一个中间操作链,对数据源的数据进行处理

终止操作(终端操作):

执行中间操作链、并产生数据结果,之后不再会被使用

流的操作:

内部迭代与外部迭代

内部迭代:

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%204.png

外部迭代:

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%205.png

ForkJoin


ForkJoin:分支合并,提供的把一个大任务分割成若干个小任务,最终汇总每一个任务结果后得到大任务结果的框架。ForkJoinPool用来实现工作窃取算法;

ForkJoin用于计算大数据量时使用,小数据量没有必要使用

工作窃取算法:

ForkJoin的工作特点就是工作窃取

  1. Fork:将一个大的任务根据条件不断地分割为互不依赖的小任务,并将这些小任务放入不同的队列(双端队列),每个工作线程维护一个任务队列,当自己维护的任务队列为空时随机从别的任务队列获取任务执行。
  2. join:最后将这些小任务的结果都放在一个统一的队列当中,最后启动一个工作线程从这个结果队列中取数据并合并到最终的结果。

工作窃取的优点与缺点:

  • 优点:充分提高程序的并行度,有效的减少了线程之间的竞争。
  • 缺点:过度消耗系统资源

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%206.png

FrokJoin简略图

ForkJoinTask

ForkJoinTask是提交给ForkJoinPool执行的任务类型

介绍:

ForkJoinTask是实现了Future接口的抽象类,实际使用中通常不继承ForkJoinTask,而是重写其继承其子类RecursiveTask或RecursiveAction的compute方法,在compute方法里实现任务的切分和结果合并。

  • RecursiveTask:用于有返回值的任务。
  • RecursiveAction:用于有没有返回值的任务。

ForkJoin的常用方法:

  • Fork:拆分任务,将任务压入线程队列
  • Join:获取某个拆分任务的结果,可能会阻塞等待

使用思路:

compute方法类似递归思想

  1. 先判断结束分割的条件是否满足,满足则执行计算并返回结果,不满足则切分为多个小任务.
  2. 然后再调用小任务的fork方法,并等待小任务的结果返回,小任务结果返回后则合并小任务的结果并将此次的最终结果返回,从而再被更大的任务合并。

异步回调


异步调用:是为了解决同步调用可能出现阻塞,导致整个流程卡住而产生的一种调用方式。

JMM


JMM:即Java内存模型,但并不真实存在,它描述的是一组规则或规范。Java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现Java程序在各种平台都能达到一致的并发效果。
Java内存模型中规定所有变量都存储在主内存,主内存是共享的内存区域,所有的线程都可以访问,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程工作内保存了该线程用到的变量和主内存的拷贝副本,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量

JUC%20a9cb6e0442e74a6ea50f6629f6a57b50/Untitled%207.png

JMM内存模型与JVM内存模型是两码事?。

主内存:

主要存储的是Java实例对象,所有线程的创建的实例对象都存放在主内存中,该实例对象是成员变量也包括了共享类的信息、常量、静态变量。由于是共享数据区域,多线程对同一个对同一个变量进行访问可能会发生线程安全问题。

工作内存:

主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝), 每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。

JMM内存模型定义:

  1. 原子性:原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
  2. 可见性:可见性指当一个线程修改共享变量的值,其他线程能够立即知道被修改了。
  3. 有序性:在并发情况下,能够让线程按代码从上往下按顺序进行执行,可以使用synchronized或者volatile保证多线程之间操作的有序性(这种是在没有指令重排的情况下)

以上3个可理解为Java并发的基础

数据同步八大原子操作:

  1. lock(锁定):作用于主内存的变量,把一个变量标记为一条线程独占状态
  2. unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
  5. use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
  6. assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
  7. store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
  8. write(写入):作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中

同步规则分析

1)不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中

2)一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量。即就是对一个变量实施use和store操作之前,必须先自行assign和load操作。

3)一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现

4)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量之前需要重新执行load或assign操作初始化变量的值。

5)如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。

6)对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)

volatile关键字:

volatile是Java虚拟机提供的轻量级的同步机制

特点:

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

volatile如何保证原子性:

使用原子包工具类

指令重排:

指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序.

一条汇编指令的执行是可以分为很多步骤的, 分为不同的硬件执行

  • 取指 IF
  • 译码和取寄存器操作数 ID
  • 执行或者有效地址计算 EX (ALU逻辑计算单元)
  • 存储器访问 MEM
  • 写回 WB (寄存器)

既然指令可以被分解为很多步骤, 那么多条指令就不一定依次序执行.

CAS(Compare and swap)


CAS:简单来说,比较和替换使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。
作用:可以解决多线程并发安全的问题

ABA问题

可重入锁(递归锁)


死锁排查


  • 第一步:jps -l 查看java进程信息
  • 第二步:jstack 查看具体的进程信息

单词


  • Reentrant:可重入的、重进入

  • signal:信号、暗号、标志、发信号、重大的,显要的;

  • **factor:**因子、因素、要素、因数

  • callable:可赎回的、可缴纳的

  • release:释放、放出、放走

  • capacity:容量、容纳能力、容积、职位、职责

  • poll:n.投票、民意调查、计票

  • atomic:原子的、原子能的

  • policy:n.政策、方针、原则、为人之道

  • reject:v.拒接、拒接接受

  • discard:v.丢弃、抛弃

  • predicate:v.断言、阐明、表明、使....为依据 n.谓语

  • recursive:递归、递归的、循环的

  • synchronize:v.同步、在时间上一致,同时进行

  • asynchronous:adj.异步、不同时发生的

  • compare:v.比较、对比、与...类似(或相似)

  • swap:v.互换 、交换(东西)、交换(工作);把...换成

源码分析

Start date: April 15, 2021

End date :

Author: ikart

Q.E.D.


在等花开,等春天来.