多线程: 正确的应用场景+合适的线程数量 = 快速运行速度
场景:cpu密集型程序 与IO密集型程序
多核CPU 处理 CPU 密集型程序,我们完全可以最大化的利用 CPU 核心数,应用并发编程来提高效率
因为系统IO的时候是cpu是处于空闲的,可以使用多个线程来利用这个cpu空闲时间。所以于IO密集型程序来说,IO所占比例越高,可以越多创建线程。
cpu密集型程序线程数 = cpu核数+1 (计算(CPU)密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。)
IO密集型程序线程数 = (IO时间/cpu占用时间+1)*cpu核数
Synchronized / Lock 区别:
1.synchronized 是Java关键字,由JVM底层实现 Lock是API层调用的类 在java.current.util.包下
2.synchronized 是非公平锁, Lock默认是非公平锁,也可设置为公平锁.
3.synchronized 执行完代码块自动释放锁,Lock需要手动unlock()来释放锁,不释放容易造成死锁
4. synchronized 通过Object类的wait,notify/notifyAll 随即唤醒或者唤醒全部,无法指定唤醒. Lock 通过Condition可以实现指定唤醒 await() signal()
jdk1.8 后synchronized 会有锁升级
偏向锁 -》 轻量级锁 -》 重量级锁 升级原理: 根据两个参数— 1.当前等待锁的线程数 2. 等待的时间
偏向锁 : 倾向于第一个来获取锁的线程,就表示一下当前线程指针 适用于没有锁争抢的情况
轻量级锁: CAS自旋锁
AQS
status 如果有人占锁就+1
AQS 定义了两种资源共享方式:
1.Exclusive:独占,只有一个线程能执行,如ReentrantLock
2.Share:共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier
ReentrankLock 原理图 status + 线程等待队列
Volatile 轻量级同步机制:
可见性(及时通知机制) 不保证原子性(完整性) 禁止指令重排
i++是线程不安全的 i++分为三个步骤 多线程执行统一资源i++时会出现覆盖的情况
保证原子性解决办法 AtomicInteger
有序性:指令重排。编译器可能不按顺序编译代码
语句执行顺序 经过指令重排后可能有三种情况(不具有数据依赖性的并行语句)
单例模式 懒汉式-doublecheck 变量加volatile防止指令重排
1.分配对象内存空间 2.初始化对象 3.将instance指向分配的内存地址
正常顺序 1 2执行完 3判断instance才不为空,如果指令重排1 3 2会造成instance为空
AutomicInteger 保证原子性的底层原理是CAS
CAS原理:compareAndSwap
unSafe类,提供CAS基于底层cpu原语的操作
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
Var1 传入的对象(AutomicInteger自己),
Var2 内存地址偏移量(在内存地址中的位置)
Var4 期望的值
Var5 目标值,(前后比较成功就去改变的值)
unSafe类 利用 CAS利用的是底层CPU并发原语
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
//自旋锁
do {
var5 = this.getIntVolatile(var1, var2); //直接取内存中的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//再次从内存地址取值 与var5比较相同则退出循环,否则重新取值
return var5;
}
CAS底层直接操作cpu原语进行操作 ,原语底层还加了锁 (如果是多核cpu 就会锁住—直接锁总线 防止多个cpu同时执行这句原语)
CAS缺点:
- 容易多次比较不成功循环时间长,开销大
- 只能保证一个共享变量的原子问题
- 引发ABA问题
ABA问题:狸猫换太子
a,b两个线程同时操作公共资源A
由于时间差,a执行的慢,b将A改成B,随后又改成A,然后a再执行。在a看来CAS比较通过,但实际上已经改变过了。
解决ABA问题:
原子引用 AtomicReference<User> ato = new AtomicReference<>();
添加版本号操作,
每次compareAndSwap时比较版本号 控制版本,当前版本小于最新版本修改失败
AtomicStampedReference ato = new AtomicStampedReference(Integer init,int 版本号);
集合类的并发安全问题:
ArrayList 多线程不安全 多线程对同一个ArrayList执行add()方法,造成并发修改异常
集合安全的List: Vector Collections.SynchronizedList() CopyOnWriteArrayList
Vector:的底层是add()方法加了Synchronized 并发安全,但是效率低
SynchronizedList原理和Vector相同,只是在原list的add(),set()方法外套一层synchronized,对象锁锁的是静态内部类SynchronizedCollection他自己.
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
CopyConWriteArrayList:原理,读写分离思想,
增删改的时候加上了lock锁,然后拷贝一份原数组扩容1再在末尾加上新元素.
适用于查看远远超过增删改的场景,增删改操作拷贝数组十分消耗内存浪费资源
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
公平锁与非公平锁 区别:
可重入锁(递归锁): 同一线程外层函数获取到锁之后,内层递归函数页获取到该锁 (把大门锁上,里边的厕所,厨房自动获取该锁)
synchronized / Reentrantedlock 都是可重入锁
自旋锁: 获取不到锁的线程不会立即阻塞,而是会一直反复循环尝试去获取锁 优点:减少线程切换的消耗,缺点: 容易多次循环高额占用cup
利用原子引用CAS思想 AtomicReference<Thread>, while循环 重复的作比较 当前线程是否为原子引用中的线程.如果不是重复循环,直到是退出循环,完成自旋
//模拟卖票 采用自旋锁
class tickets{
int num = 3000;
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void sale(){
while (num<=0){
return;
}
//mylock();
System.out.println("当前进程"+Thread.currentThread().getName()+"抢到一张票 还剩"+--num+"张");
// myunlock();
}
public void mylock(){
Thread t = Thread.currentThread();
while(!atomicReference.compareAndSet(null,t)){
System.out.println(t.getName()+"进不去啊...");
}
}
public void myunlock(){
Thread t = Thread.currentThread();
while(!atomicReference.compareAndSet(t,null)){
System.out.println(t.getName()+"出不来啊...");
}
}
}
ReentrantReadWriteLock 读锁共享锁,写锁独占锁
原理:实现读写分离的锁操作
使用ReentrantReadWriteLock.ReadLock读锁加锁后,被读锁加锁的代码块可以不原子执行(读操作可以被加塞)
使用ReentrantReadWriteLock.WriteLock写锁加锁后,被写锁加锁的代码块必须原子执行(写操作中途不能被加塞)
CountDownLauch 火箭发射倒计时 (用于控制一些前提线程全部完成后,后边再执行)
CountDownLauch countDownLauch = new CountDownLauch(6); 创建一个倒计时器,构造器传参倒计时数,当计数到0时,解除countDownLauch.await的阻塞
countDownLauch.countDown(); //用于计数-1. countDownLauch.await() //计数到0之前保持阻塞.
CyclicBarrier 凑齐七个龙珠召唤神龙
Semaphore 信号灯 抢车位
Semaphore semaphore = new Semaphore(5); //五个停车位
semaphore.acquire(); //占用一个停车位
semaphore.release(); //放开一个停车位
阻塞队列 BlockingQueue
ArrayBlockingQueue LinkedBlockingQueue SynchronizedQueue(只能存储一个元素,生产一个消费一个)
抛异常方法 返回布尔值 阻塞 超时
插入: add(e) offer(e) put(e) offer(e,time,util)
移除: remove() poll() take() poll(time,util)
检查: element() peek() 不可用 不可用
高内聚低耦合线程操作资源类 判断 干活 通知
线程池:
ExcutorService threadPool = newFixedThreadPool(int nThreads); //创建固定大小线程池
ExcutorService threadPool = newSingleThreadExecutor(); //创建单一线程的线程池
ExcutorService threadPool = newCachedThreadPool(); //根据缓存能力创建不限大小的线程池
threadPool.execute(Runnable task); //执行线程,传入一个runnable接口,无返回值
threadPool.submit(Runnable task); //传入runnable接口,返回值FutureTask
threadPool.submit(Callable<T> task); //传入callable接口,返回值FutureTask
threadPool.submit(Runnable task,T result); //传入runnable接口,result返回结果类型,返回值FutureTask
threadPool.shutdown(); //关闭
线程池ThreadPoolExecutor()七大参数:
corePoolSize ,常驻线程数
maximumPoolSize ,最大线程数
keepAliveTime ,多余线程存活时间
TimeUnit ,时间工具类
BlockingQueue ,阻塞队列(候客区)
threadFactory ,生成线程池中工作线程
handler ,拒绝策略
线程池不断地execute()过程, 使用常驻线程--> 使用阻塞队列 --> 扩容使用最大线程数 -->拒绝策略. 多余线程无事可做到一定时间keepAliveTime&&当前线程数大于常驻线程数,多余线程停掉
四个拒绝策略:AbortPolicy(默认) :直接抛异常阻止系统运行
CallerRunsPolicy : 调用者线程回调
DiscardOldestPolicy : 抛弃队列中等待最久的任务,然后把当前任务加入队列尝试提交
DiscardPolicy : 直接抛弃当前任务 (最好策略)
生产环境中,线程池不允许使用Executors去创建,而是通过new ThreadPoolExecutor()手写创建
死锁:持有自己的锁,还妄图得到别的人锁
若无外力干涉,始终僵持进行下去
解决死锁办法:
可以加信号灯 Semaphore
每个线程先尝试获取信号灯,再尝试获取资源的锁
排查线程故障:
1.使用jps 指令在windows下查看java线程。指令 jps -l 找到进程号
2.使用jstack + 进程号 指令 打印出该线程错误信息
关于ThreadLocal
ThreadLocal 是为了解决资源不能被多线程共享访问的问题
每个Thread内部维护了一个ThreadLocalMap默认为NULL。当ThreadLocal set值时 将(K,V)设置成Entry存入ThreadLocalMap的数组table中,再将ThreadLocalMap存入Thread ThreadLocalMap由多个Entry<K,V>组成, 每个Entry<K,V> 的key就是ThreadLocal 和value组成
(我看过了源码 ThreadLocalMap 底层是Entry<K,V>数组 每个Entry保存一个value,Entry数组的K可以重复,不能K和V同时重复。。 K的值就是ThreadLocal )
多线程需要共享访问的资源 复制多份 存入每个线程的 ThreadLocal,互不干涉的访问
内存泄漏问题:
ThreadLocal中的key是由弱引用存储 下一次GC就会回收掉key 但是value还是存在的
导致 只要是该线程还存活 这个value就永远不能被回收 占用内存
破解办法就是 每次使用完 调用ThreadLocla的 remove方法 把值remove调
ThreadLocal用完一定要即时remove调 否则可能会产生脏数据(线程池重复利用同一个Thread容易使用了脏数据),还会造成内存泄漏 如上
关于ComparebleFuture
异步处理任务 不需要等待任务完成可以继续执行 回过头来再查看任务执行情况
还可以链式执行任务,上个任务执行完 才执行下一个任务,下一个任务的参数用上一个任务的结果等