回顾多线程
线程生命周期
NEW RUNNABLE BLOCKED WAITTING TIME_WAITTING TERMINATED
wait/sleep区别
wait来自object,sleep来自thread
wait会释放锁,sleep不会释放锁
使用范围:
wait 需要在同步代码块中使用
sleep可以在任何地方使用
lock锁
实现类:
ReentranLock ReentrantReadWriteLock ReadLock ReentranReadWriteLock WriteLock
syncronized与lock 的区别:
- sy是java的关键字,lock是一个java类
- sy 无法判断获取锁的状态,lock可以判断是否获取锁
- sy会自动释放锁,lock必须手动释放,否则会发生死锁
- sy被一个线程获得后另外线程只能等待,lock就不会
- sy可重入锁,不可以中断,非公平;lock可重入锁,可以判断锁,默认非公平(可以自己设置)
- sy适合锁少量的代码;lock适合锁大量的代码
传统生产者与消费者、防止虚假唤醒
生产者消费者模式
package com.kuang.pc;
/**
* @author S_J
* @version 1.0
* @date 2022/10/14 20:09
*/
public class A {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i=0;i<10;i++){
try {
date.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
date.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//等待 业务 通知
class Date{
private int number = 0;
//+1
public synchronized void incr() throws InterruptedException {
if(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
if (number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
当生产者与消费者各位两个实例是,A、C为生产者B、D为消费者
生产者生产完毕后处于等待,阻塞住,唤醒两个消费者
消费者C被唤醒后抢到锁并消费后又处于等待,等待的时候会释放锁
这时消费者D会在wait()后面继续执行
由于前面判断是if
所以不会返回再判断
就继续往下执行
当将if改为while判断语句时
在程序要跳出循环前会进行语句判断
B减过一次资源已经为0
D唤醒后就会进入循环发现为0就会进入循环体就会调用wait()方法进入等待
八锁问题
CopyOnWriteArrayList
并发修改异常
//Exception in thread "Thread-12" java.util.ConcurrentModificationException并发修改异常
public class ListTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 1; i <=10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}).start();
}
}
}
解决:
改用安全的集合如Vectory或者用Collection.synchronized或者CopyOnWriteArrayList(写入时复制)
另外针对上面代码而言,新加入后集合长度改变了继续使用该集合进行遍历会报并发修改异常,因此当我们add()后就break循环,就重新进行获取新的已经替代后的集合状态
线程池
线程池的好处:降低资源消耗、提高响应的速度、方便管理、线程复用可以控制最大并发数、管理线程
三大方法、七大参数、四种拒绝策略
三大方法:Executors
工具类的三个创建线程的方法,分别为:单个SingleThreadPool
、固定大小FixedThreadPool
、伸缩可变大小CachhedThreadPool
七大参数:核心、最大线程数、超时等待、超时单位、阻塞队列、线程工厂、拒绝策略
四种拒绝策略:AbortPolicy
默认拒绝策略,拒绝任务并抛出任务、CallerRunsPolicy
使用调用线程直接运行任务、DiscardPolicy
直接拒绝任务,不抛出错误、DiscardoldestPolicy
触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入
最大线程到底如何定义?如下
CPU密集型IO密集型
CPU密集型:几核就定义为几,可以保证效率最高。获取CPU的核数:sout(Runtime.getRuntime().availableProcessors())
IO密集型:判断程序中十分耗IO的线程。如15个大型任务,Io十分占用资源,就一般设置其两倍大小30个
函数型接口和判定型接口
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
只有一个方法的接口
四大函数式接口
Function:函数式接口,有一个输入参数,有一个输出
只要是函数式接口 就可以用lambda表达式简化
Predicate:断定行接口
有一个输入参数,返回值只能是布尔值
Comsumer:消费型接口
只有输入没有返回值
Supplier:供给型接口
Stream流式计算
集合+mysql本质就是存储数据的东西
计算都交给流来操作!
ForkJoin
1.7出来的,并行执行任务,提高效率。
把大任务拆分成小任务,计算完进行汇总
特点:工作窃取(A线程执行到中途,B线程先执行完后去帮助A线程执行来体高效率)
如何使用ForkJoin:
forkjoinPool:通过他来执行
计算任务:forkjoinPool.execute(ForkJoinTask task)
一般计算、forkjoin、并行流
异步回调
理解JMM
Volatile
保证可见性
禁止指令重排
不保证原子性
不加sy或者lock的情况下怎么保证原子性
使用原子类解决
彻底明白单例
破坏单例模式:
单例代码的实现是使用双重检测模式,懒汉式单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy instance = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
CAS
ABA问题
原子引用
理解各种锁
公平锁:先来后到
非公平锁:可以插队
可重入锁(递归锁)
自旋锁
不断尝试,直到成功为止