java多线程:详解JUC

对应狂神说JUC视频
1.JUC是什么
java.util下的几个包的简称 涉及到多线程的开发
java.util.concurrent
java.util.atomic
java.util.concurrent.locks


2.
线程和进程
进程:多个程序的集合
线程:进程中的一个执行命令
一个进程往往有多个线程
java默认是有2个线程?main GC
Thread Runnable Callable
java真的可以开启线程嘛?开不了 (实际调用底层的native方法,用c++去操作硬件)

并发 并行
并发 多线程操作同一个资源 同一时间下多个任务按照顺序交替执行
并行 多个人一起执行  同一时间下多个任务同时执行
CPU单核 模拟出多条线程 快速交替
CPU多核 多个线程同时执行 线程池
并发实际上轮流处理多个任务 并行是同时处理多个任务

并发编程的本质:充分利用CPU的资源

3.线程有几个状态
NEW 新生
RUNNABLE 就绪
BLOCKED 阻塞
WAITING 等待
TIMED_WAITING 超时等待
TERMINATERD 终止

wait/sleep区别
1.wait-》Object sleep ->Thread
2.关于锁的释放:wait会释放锁 sleep不会
3.使用范围不同:wait必须在同步代码块中 sleep可以在任何地方执行
4.是否需要捕获异常:sleep是必须捕获异常,wait也需要的,notify 和notifyAll不需要

线程常用方法
sleep 当前线程阻塞的毫秒数 可以模拟网络延时 倒计时
join athread.join() 当前线程等待a线程终止 a线程插队
yield this.yield 让出当前cpu 谁执行谁礼让
停止线程 设置标志位

 

死锁 某一个同步块同时拥有“两个以上对象的锁”

4.锁
synchronized
new Thread(()->{}).start();
//lambda表达式的方式实现多线程 -》用匿名内部类实现Runnable接口,再用线程对象开启线程
// 函数式接口方便用lambda表达式构造出实例

5.Lock
reentrantLock 可重入锁 reentrantReadWriteLock.readlock reentrantReadWriteLock.writeLock
公平锁 先来后到
非公平锁 可以插队 (默认)
1)new ReentrantLock();
2)Lock.lock();//加锁
3)finally-> lock.unlock();//解锁


6. lock和synchronized 区别
1)synchronized 内置的java关键字,lock是一个java类
2)synchronized 无法判断获取锁的状态 lock可以判断
3)synchronized 会自动释放锁,lock需要手动加锁 手动释放锁,可能会遇到死锁
4)synchronized 线程1(获得锁-》阻塞)线程2(等待);lock会有一个trylock尝试获取锁,不会造成长久的等待
5)synchronized 是可重入的,不可以中断的,非公平锁,lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁
6)synchronized 适合锁少量的代码同步问题,lock适合锁大量的同步代码

可重入锁(递归锁)
在外层使用锁之后,内层依然可以使用,并不发生死锁
拿到了外面的锁 就可以自动获取里面的锁
一个类的A B两个方法 A B 都有同一个锁,A方法调用,获得锁,在A方法的锁还没有被释放,B方法也获得该锁


7.生产者和消费者问题
1).管程法 : 设置缓冲区 生产者将生产好的数据放入缓冲区 消费者从缓冲区拿出数据
2).信号灯法:设置一个标志位 控制生产 消费
synchronized wait notifyAll
四个线程 出现虚假唤醒问题 if改为while
lock版
Lock condition.await();condition.signalAll();
condition 精准的通知和唤醒线程

10.8锁现象
(1.先发短信
(2.先发短信
synchronized 锁的对象是方法调用 两个方法拿到的同一个锁,谁先拿到谁执行
(3.加入一个普通方法
先hello
不受synchronized锁的影响,不用等待锁的释放
(4.两个方法拿的不是一个对象
先打电话
两个对象两把锁,不会出现等待
(5.synchronized方法变成静态方法
始终先发短信
锁的是class类模板
(7.一个静态同步 有一个非静态同步
先打电话
不是一个对象


11.集合
1)CopyOnWriteArrayList 线程安全 底层是使用Lock锁
写入时复制 读的时候没有锁写的时候加锁
2)set不安全
使用Collections工具类的synchronized包装的Set类
Set<String> set = Collections.synchronizedSet(new HashSet<>());
使用CopyOnWriteArraySet 写入复制的JUC解决方案
hashSet底层就是一个hashMap
3)Map不安全
hashMap 并发修改异常
ConcrrentHashMap

Callable
可以有返回值
可以抛出异常
方法不同 run()/call()

get() 方法获取返回值 可能会产生阻塞
结果有缓存,同一个方法再次调用只会打印一次

常用的辅助类
1)CountDownLatch 减法计数器 一次性
2)CyclickBarrier 加法计数器
3)semaphore 信号量

 

18.读写锁 ReentrantReadWriteLock
写的时候只有一个线程写,读的时候可以多个线程一起读
独占锁(写锁)共享锁(读锁)


19阻塞队列
队列先进先出
写入:如果队列满了不得不阻塞 取:如果队列是空的,必须阻塞
什么情况下会使用阻塞队列:多线程并发处理 线程池


20BlockingQueue 有四组api
方式 抛出异常 不会抛出异常,有返回值 阻塞,等待 超时等待
添加 add offer put offer(timenum.timeUnit)
移出 remove poll take poll(timenum,timeUnit)
判断队首元素 element peek - -

21同步队列
SynchronousQueue put一个元素,必须take 才能put


22线程池
三大方法 7大参数 4种拒绝策略
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,提高效率
好处:
1)降低资源的消耗 (不用每次自己创建线程)
2)提高响应速度 (不用等线程创建就能立即执行)
3)方便管理 (线程池可以统一分配 调优 监控)
线程复用 可以控制最大并发数 管理线程
方法:
Executors.newSingleThreadExecutor();//单个线程
Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
Executors.newCachedThreadPool();//可伸缩的


线程池不允许用Executors去创建 而是通过ThreadPoolExecutor的方式
原因:FixedThreadPool SingleThreadPool 允许队列长度太长,可能会堆积大量请求
CachedThreadPool 允许的创建线程数量太大

public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue,// 阻塞队列
ThreadFactory threadFactory,// 线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler// 拒绝策略
)

超时等待 超时没人调用 最大线程池线程会释放

拒绝策略
AbortPolicy 满了 不处理新数据,抛出异常
CallerRunsPolicy 由调用线程处理
DiscardPolicy 丢弃任务,不抛出异常
DiscardOldestPolicy 队列满了,尝试和最早的竞争,不会抛出异常


24如何设置线程池大小
CPU密集 获取CPU的核数
IO密集型 程序中 有多少个大型任务 设置为1~2倍


25-26函数式接口
传统技术必会:泛型 枚举 反射
lambda表达式 函数式接口 stream流式计算 链式编程
链式编程 @Builder
可读性高 代码更简洁
不利于调试
跳过


27Stream流式计算
集合 mysql本质就是用来存储东西的
计算应该交给流来操作


28 ForkJoin
特点:工作窃取 先执行完毕的进程会把没执行完的进程窃取过来执行
双端队列
ForkJoinPool 可以将任务分成多个子任务 放进线程池中 再合并结果
steam流
long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum); //用一个并行流计算


异步回调
Future 对未来的某个事件结果进行建模
CompletableFuture
(1)没有返回值的runAsync异步回调
(2)有返回值的supplyAsync异步回调

30.JMM
java内存模型
volatile 是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排

如何实现可见性

volatile变量修饰的共享变量在进行写操作的时候回多出一行汇编:

0x01a3de1d:movb $0×0,0×1104800(%esi);0x01a3de24:lock addl $0×0,(%esp);

Lock前缀的指令在多核处理器下会引发两件事情。

​ 1)将当前处理器缓存行的数据写回到系统内存。

​ 2)这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。

多处理器总线嗅探:

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。


JMM :不是一个存在的东西,而是一个关于同步的约定:
作用:保证线程的安全
1)线程解锁前,必须把共享变量刷回主存
2)线程加锁前,必须读取主存中的最新值到工作内存中
3)加锁和解锁是同一个锁


8种操作
read load use assign(赋值) write store
lock unlock

制定规则:
不允许read load store和write操作之一单独出现, 用了read必须load,使用了store必须write
不允许线程丢弃最近的assign操作,即工作变量的数据变了,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个变量同一时间只有一个线程能对其lock
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值
如果一个变量没有被lock,就不能对其进行unlock

原子性:一组操作,要么全部执行,要么全部不执行
可见性:只要一个线程对共享变量的值做了修改,其他线程都将马上收到通知,立即获得最新值
有序性:对于单线程的代码,我们总是认为程序是按照代码的顺序进行执行,多线程下,就可能发生指令重排


31.volatile 可见性及非原子性验证
volatile 不保证原子性
lock synchronized 保证原子性

使用原子类 解决原子性
number.incrementAndGet(); //底层是CAS保证的原子性
在内存中修改值

32.禁止指令重排
volatile 会加一道内存屏障 这个内存屏障可以保证在这个屏障中的指令顺序
禁止上面指令和下面指令顺序交换

33.DCL懒汉式
饿汉式 浪费空间
懒汉式
多线程并发 创建了多个对象
双重检测锁:如果多个线程同时通过了第一次检查,其中一个线程首先通过了第二次检查并new对象,
其他线程不会再去实例化对象

实例化对象不是原子性操作,怎么解决?
加volatile 防止指令重排

利用静态内部类 创建唯一对象
可以用反射破坏
枚举可以防止反射破坏
原因:反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败

CAS
原子类保证原子性 利用的底层的CAS 操作
native https://blog.csdn.net/wike163/article/details/6635321
一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。
Unsafe类 java通过这个类操作内存

CAS compareAndSet 比较并交换
incrementAndGet 当前内存地址中的值 当前对象的内存地址偏移值 相同,当前内存地址值+1
比较当前工作内存中的值和主内存的值 如果这个值是期望的 那么则执行操作 如果不是就一直循环 使用的是自旋锁
缺点:循环会耗时
一次性只能保证一个共享变量的原子性
它会存在ABA问题


CAS ABA问题
在CAS操作时,其他线程将变量值A改成了B,然后又改回了A。
等到本线程使用期望值A与当前变量进行比较时。发现变量A没有变,于是CAS就将A值进行了交换操作

原子引用:带版本号的原子操作
解决ABA问题:带版本号的原子操作
对应思想:乐观锁(只是在执行更新的时候判断一下在此期间别人是否修改了)


36.可重入锁(递归锁)
拿到了外面的锁 就可以自动获取里面的锁
1)lock锁必须配对,相当于lock和 unlock 必须数量相同;
2)在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
Java中ReentrantLock和synchronized都是可重入锁

37.自旋锁
getIntVolatile
拿不到锁的线程一直循环等待


38.死锁
线程1持有资源A,线程2持有资源B,同时想申请对方的资源,互相等待造成死锁
死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用
2.请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源不释放
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

怎么判断死锁
jps -l 查看进程号
jstack 进程号 -》查看堆栈信息 找到死锁问题


 

posted @ 2023-02-22 19:35  下饭  阅读(160)  评论(0编辑  收藏  举报