本笔记为本人在 B 站学习时整理而成,为了更好的学习,将其整理成笔记,以防忘记相关知识点。
参考 B 站狂神 JUC 教程!!!!我认为狂神讲的挺不错的!
1、环境准备
idea 创建项目,记得把Project Structure
中的Lauguage level
改为Lambda 8
。
将settings->Build,Execution,Deployment->Compiler->Java Compiler
中的编译版本改为 8。
可以在设置中将以后创建新项目的默认编译版本改为 jdk8。(other settings)
即把项目使用的 JDK 版本全部改为 8。
2、什么是 JUC
*源码+官方文档 * 面试高频问!
打开 jdk1.8 帮助文档:
具体要学习的:(java.util 工具包中的几个包中的类)
JUC (java.util.concurrent)
业务:普通的线程代码 Thread
Runnable
没有返回值、效率相比于Callable
较低!
企业开发中 Runnable 使用的比较少,更多执行任务的时候会使用 Callable!
3、线程和进程
线程、进程,如果不能使用一句话说出来的技术,说明你的基本功不扎实!!!
进程:一个程序, 比如 QQ.exe Music.exe 程序的集合
一个进程往往可以包含多个进程,至少包含一个!!!
Java 默认有就几个线程? 2 个 main 线程 和 GC 线程(垃圾回收)
线程:例如开了一个进程 Typora,写字的时候,自动保存(这是由线程负责的)
对于 Java 而言:Thread、Runnable、Callable
Java 真的可以开启线程吗? 开不了
1 | //打开 new Thread().start(); 的start方法进去看源码 |
只能通过调用本地方法,本地方法的底层是 C++,C++可以操作硬件。
并发、并行
并发编程:并发、并行
并发(多线程操作同一个资源)
:CPU 一核,模拟出来多条线程,天下武功唯快不破,快速交替。
并行(相当于多个人一起行走):
CPU 多核,多个线程可以同时执行; 线程池。
可以通过代码查看电脑的核数:
1 | package com.itjing.juc; |
并发编程的本质:充分利用 CPU 的资源
所有的公司都很看中这一点。
企业,挣钱=> 提高效率,裁员,找一个厉害的人顶替三个不怎么样的人;
人员(减) 、技术成本(高)
线程有几个状态
1 | /*根据 Thread.State 进入State 枚举类 |
wait/sleep 区别
1、来自不同的类
wait => Object
sleep => Thread
企业中线程休眠不会使用 sleep
一般使用 java.util.concurrent.TimeUnit
例如睡眠 1 秒
1 TimeUnit.SECONDS.sleep(1);
2、关于锁的释放
wait 会释放锁。
sleep 会抱着锁睡觉,不会释放锁。
3、使用的范围是不同的
wait 必须在同步代码块中。
sleep 可以在任何地方使用。
4、Lock 锁(重点)
有的人的线程还是这样写的:
1 | public class sellTicketDemo01 { |
在公司中,不是这样用的。公司中的开发一定要降低耦合性,如果实现Runnable接口的话,就只会仅仅变成一个线程类。
传统 Synchronized
1 | package com.itjing.juc; |
使用 Lock 锁
Lock 接口
查看官方文档
有三个实现类:
可重入锁是常用的。
1 | Lock lock = new ReentrantLock(); //查看其源码 |
公平锁:十分公平,先来后到。
非公平锁:十分不公平,可以插队(默认)
修改菜单中code->surround with的快捷键。
,我设置成了,ctrl+alt+1
。
1 | package com.itjing.juc; |
Synchronized 和 Lock 的区别
1、Synchronized 内置的 Java 关键字; Lock 是一个 Java 类 。
2、Synchronized 无法判断获取锁的状态;Lock 可以判断是否获取到了锁。
3、Synchronized 会自动释放锁;Lock 必须要手动释放锁!如果不释放锁,会出现死锁
现象 。
4、Synchronized 线程 1(获得锁,阻塞)、线程 2(等待,傻傻的等);Lock 锁就不一定会等待下去。
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以 自己设置)。
6、Synchronized 适合锁少量的代码同步问题; Lock 适合锁大量的同步代码。
锁是什么,如何判断锁的是谁?
5、生产者和消费者问题
面试会问的知识点:单例模式、排序算法、生产者和消费者、死锁
生产者和消费者问题 Synchronized 版
1 | package com.itjing.juc.pc; |
问题存在,如果存在 A B C D 4 个线程,即两个以上的线程! 会出现虚假唤醒现象!!!
if 改为 while 判断
1 | public class A { |
JUC 版的生产者和消费者问题
通过 Lock 找到 Condition
代码实现:
1 | package com.itjing.juc.pc; |
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充!
Condition 精准的通知和唤醒线程
出现的问题,线程并不是有序的调用。
如果我们想有序的进行线程调用,可以这样做,精准的通知和唤醒指定的线程:
代码测试:
1 | package com.itjing.juc.pc; |
6、8 锁现象
如何判断锁的是谁!永远的知道什么锁,锁的到底是谁!
对象、Class
深刻理解锁
8 锁,就是关于锁的 8 个问题
第一组锁
1 | package com.itjing.juc.lock8; |
第二组锁
1 | package com.itjing.juc.lock8; |
第三组锁
1 | package com.itjing.juc.lock8; |
第四组锁
1 | package com.itjing.juc.lock8; |
小结 锁的到底是什么
new : this 具体的一个手机
static : Class 唯一的一个模板
7、集合类不安全
List 不安全
1 | package com.itjing.juc.unsafe; |
狂神的学习方法推荐:1、先会用、2、货比 3 家,寻找其他解决方案,3、分析源码!
Set 不安全
1 | package com.itjing.juc.unsafe; |
hashSet 底层是什么? HashMap
1 | public HashSet() { |
Map 不安全
HashMap 源码:
1 | /** |
1 | package com.itjing.juc.unsafe; |
8、Callable(简单)
1、可以有返回值
2、可以抛出异常
3、方法不同,run() / call()
代码测试
FutureTask中有这样一个构造方法:
代码案例
1 | package com.itjing.juc.callable; |
细节:
1、有缓存!
2、结果可能需要等待,会阻塞!
9、常用的辅助类(必会)
CountDownLatch
代码案例
1 | package com.itjing.juc.add; |
原理:
1 | countDownLatch.countDown(); // 数量-1 |
CyclicBarrier
代码案例
1 | package com.itjing.juc.add; |
Semaphore
Semaphore:信号量
抢车位!
6 车—3 个停车位置
代码实现:
1 | package com.itjing.juc.add; |
原理:
semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release(); 释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用!并发限流,控制大的线程数!
10、读写锁
ReadWriteLock
代码案例:
1 | package com.itjing.juc.rw; |
11、阻塞队列
阻塞队列:
BlockingQueue :BlockingQueue 不是新的东西
什么情况下我们会使用 阻塞队列: 多线程并发处理,线程池!
学会使用队列
添加、移除
四组 API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put() | offer( , , ) |
移除 | remove | poll | take() | poll( , ) |
检测队首元素 | element | peek | - | - |
代码实现:
1 | package com.kuang.bq; |
SynchronousQueue 同步队列
没有容量,
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put、take
1 | package com.itjing.juc.bq; |
学了技术,不会用! 看的少!
12、线程池
线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!
优化资源的使用!=>池化技术
线程池、连接池、内存池、对象池///….. 创建、销毁。十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。
*线程池的好处: *
1、降低资源的消耗
2、提高响应的速度
3、方便管理。
线程复用、可以控制最大并发数、管理线程
线程池:三大方法
1 | package com.itjing.juc.pool; |
7 大参数
源码分析:
1 | //通过源码可知,三大方法都是用ThreadPoolExecutor实现的 |
手动创建一个线程池
1 | package com.kuang.pool; |
4 种拒绝策略
1 | /** |
小结和拓展
池的大的大小如何去设置!
了解:IO 密集型,CPU 密集型:(调优)
1 | public class Demo01 { |
13、四大函数式接口(必须掌握)
新时代的程序员必须要会:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口: 只有一个抽象方法的接口,可以有默认方法
例如 Runnable 接口:
1 | public interface Runnable { public abstract void run(); } |
在 java.util.function 包中,有这样四大函数式接口:
函数式接口(Function)
代码案例:
1 | package com.itjing.juc.function; |
断定型接口(Predicate)
断定型接口: 有一个输入参数,返回值只能是布尔值
代码案例:
1 | package com.itjing.juc.function; |
消费型接口(Consumer)
代码案例:
1 | package com.itjing.juc.function; |
供给型接口(Suplier)
代码案例:
1 | package com.itjing.juc.function; |
14、Stream 流式计算
什么是 Stream 流式计算
大数据: 存储+计算
集合、MySQL 本质就是存储东西的;
计算都应该交给流来操作!
代码案例:
1 | package com.itjing.juc.stream; |
1 | package com.itjing.juc.stream; |
重要的是查看源码,读懂源码!
15、ForkJoin
什么是 ForkJoin
ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!
大数据:Map Reduce (把大任务拆分为小任务)
分而治之,类似分治算法。
ForkJoin 特点:工作窃取
这个里面维护的都是双端队列,可以从上面取,也可以从下面取。
Forkjoin
代码案例:
1 | package com.itjing.juc.forkjoin; |
1 | package com.itjing.juc.forkjoin; |
16、异步回调
Future 设计的初衷: 对将来的某个事件的结果进行建模
代码案例:
1 | package com.itjing.juc.future; |
17、JMM
请你谈谈你对 Volatile 的理解
Volatile 是 Java 虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是 JMM
JMM : Java 内存模型,不存在的东西,概念!约定!
关于 JMM 的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的新值到工作内存中!
3、加锁和解锁是同一把锁
线程 工作内存 、主内存
8 种操作:
内存交互操作有 8 种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于 double 和 long 类 型的变量来说,load、store、read 和 write 操作在某些平台上允许例外)
1 | 1. lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态 |
JMM 对这八种指令的使用,制定了如下规则:
1 | 1. 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须 write |
问题: 程序不知道主内存的值已经被修改过了
18、Volatile
保证可见性
1 | package com.itjing.juc.myvolatile; |
不保证原子性
1 | package com.itjing.juc.myvolatile; |
如果不加 lock 和 synchronized ,怎么样保证原子性
使用 javap 命令反编译
使用原子类,解决 原子性问题
代码案例:
1 | package com.itjing.juc.myvolatile; |
这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!
指令重排
什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
1 | int x = 1; // 1 |
可能造成影响的结果: a b x y 这四个值默认都是 0;
线程 A | 线程 B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果: x = 0;y = 0;但是可能由于指令重排
线程 A | 线程 B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的诡异结果: x = 2;y = 1;
非计算机专业
volatile可以避免指令重排: 内存屏障。CPU指令。
作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性 (利用这些特性 volatile 实现了可见性)
Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
19、彻底玩转单例模式
饿汉式 DCL 懒汉式,深究!
饿汉式
1 | package com.itjing.juc.single; |
DCL 懒汉式
double check lock 双重检查锁机制
1 | package com.itjing.juc.single; |
静态内部类
1 | package com.itjing.juc.single; |
单例不安全,反射
枚举
1 | package com.itjing.juc.single; |
枚举类型的终反编译源码:
20、深入理解 CAS
什么是 CAS
大厂你必须要深入研究底层!有所突破! 修内功,操作系统,计算机网络原理
1 | public class CASDemo { |
Unsafe 类
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就 一直循环!
缺点:
1 | 1、 循环会耗时 |
CAS : ABA 问题(狸猫换太子)
1 | public class CASDemo { |
21、原子引用
解决 ABA 问题,引入原子引用! 对应的思想:乐观锁!
带版本号 的原子操作!
1 | package com.itjing.juc.cas; |
注意:
22、各种锁的理解
公平锁、非公平锁
公平锁: 非常公平, 不能够插队,必须先来后到!
非公平锁:非常不公平,可以插队 (默认都是非公平)
1 | public ReentrantLock() { |
可重入锁
可重入锁(递归锁)
Synchronized 版
1 | package com.itjing.juc.lock; |
Lock 版
1 | package com.itjing.juc.lock; |
自旋锁
spinlock
我们来自定义一个锁测试:
1 | package com.itjing.juc.lock; |
测试
1 | package com.itjing.juc.lock; |
死锁
死锁是什么
死锁测试,怎么排除死锁:
1 | package com.itjing.juc.lock; |
解决问题
面试,工作中! 排查问题:
1、日志 9
2、堆栈 1能从堆栈中看出问题的才是硬技术。
发布时间: 2020-11-19
最后更新: 2024-06-24
本文标题: 多线程进阶->JUC并发编程
本文链接: https://blog-yilia.xiaojingge.com/posts/a5484b21.html
版权声明: 本作品采用 CC BY-NC-SA 4.0 许可协议进行许可。转载请注明出处!
