对应狂神说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.禁止指令重排
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 进程号 -》查看堆栈信息 找到死锁问题
标签:JUC,java,synchronized,lock,死锁,线程,内存,多线程 From: https://www.cnblogs.com/yxj808/p/17145617.html