java多线程与高并发

volatile 线程可见性、禁止指令重排

如何保证指令重排?

内存屏障分为os与jvm两种实现

os 1.内存屏障:sfence/mfence等系统原语

2.锁总线 (多个cpu读写内存的时候,通过lock指令锁住总线,保证只有一个cpu访问内存)

jvm 1. java code 上volatile关键字

2.bytecode 也是volatile关键字

3.jvm 层面 (jsr要求实现内存屏障 在读写volatile变量前后加屏障)

4.hotspot实现 (查看OrderAccess::fence lock总线,因为这种方法所有的cpu都支持)

1 cup(ALU+REG) > 3 cache(L1/L2/ L3共享) > 100 memory > 100w disk

超线程 1个ALU对应两组pc+alu

ThreadLocal

强软弱虚引用

强引用: 不使用的变量、对象会被回收

软引用:SoftReference 堆内存不足时回收

弱引用:WeakReference 使用一次就被回收

虚引用:PhantomReference 引用不到,管理堆外内存(jvm专门的线程, DirectByteBuffer)

应用nio以及netty zero-zopy

线程切换

分为内核线程和应用线程;

java的Thread与内核线程是1:1,

而Go的纤程与内核线程是m:n(m远大于n,且纤程之间可以同步数据);

java扩展的纤程框架:Quasar、Kilim

锁的概念

多个线程访问同一个资源的时候,这个资源必须上锁才能正常访问;

锁对象可以随意指定(object或者class)

不持有锁的线程怎么办,CAS、等待队列(需要经过os调度,所以是重量级锁)、

锁的效率:看等待的线程多少,等待的人多时cas比较消耗cpu;还不如重量级锁的效率高

锁的四种状态、

多线程执行count++

synchronized 锁住代码段

juc atomicInteger –> UnSafe.compareAndSwap() CAS /自旋锁

cas: 循环执行、写入的时候检查是否是读出来的数据

  1. ABA问题:加版本号或时间戳解决

  2. CAS修改值的时候的原子性问题 Atomic::cmpxchg –> asm

synchronized锁升级过程

5250446994c342b491108a4c2663405d

偏向锁:在锁对象上加上线程id

轻量级锁(自旋锁):有线程竞争的时候,偏向锁升级为轻量级锁

重量级锁:线程重度竞争时,加入os线程调度队列

jol内存布局(8Byte的倍数)

markword

class pointer

instance data

padding data

缓存行 cacheline

1 cl = 64 Byte = 8 long

缓存行对齐 disruptor

juc

ReentrantLock

与synchronized一样,属于互斥锁

ReentrantLock lock = new ReentrantLock ();//默认非公平锁
lock.lock();//加锁
try{
    //...
}finally{
    lock.unlock();//释放锁
}

lock方法

  1. FairSync :公平锁 执行lock方法时先看是否有锁,已经加锁就先排队

  2. NonefairSync:非公平锁 先尝试获取锁,获取不到锁再加锁acquire()方法尝试获取锁,

AQS AbstractQueuedSynchronizer (CAS加volatile实现)

  1. 双向链表(queue) Node(prev/next/thread)

  2. volatile status=1就排队

acquire方法

1. 使用Unsafe.cas设置status值

2. 调用tryAcquire加锁,

可重入锁:一个线程在已经给方法加锁的情况下,还可以再次进去这个方法

tryAcquire方法

1.非公平锁 state==0判断 、获取锁并添加线程标识

2.公平锁 先判断队列是否有线程排队、state==0判断 、获取锁并添加线程标识

addWaiter()方法

获取锁失败的线程、封装为Node、排队到AQS队列

acquireQueued()方法

1.检查当前node是否排在第一个(不算head),再次执行tryAcquire

2.不为头节点的下一个节点,尝试挂起

Node

waieStatus 1取消 0正常 -1挂起

unlock() 释放锁 release()

线程池

ThreadPoolExecutor

  1. 应用方法

    为啥要用线程池?

    Executors提供封装好的线程池、实践中无法满足要求

  2. 7个参数

    corePoolsize //核心线程数

    maxinumPoilSize //最大线程数

    keepAliveTime/unit 非核心线程空闲时间/单位

    workQueue //阻塞队列

    threadFactory //线程工厂(配置线程名)

    rejectExecHandler //拒绝策略(阻塞队列满了) 几种策略abort/caller/discardOld/discard

  3. 执行流程

    任务提交到线程池后,线程池的处理流程

    execute() : 工作线程是否小于核心线程(是的话直接创建新线程)、判断工作队列是否满(否直接排队)、工作线程数是否达到最大线程数(创建非核心线程并执行当前任务)、走拒绝策略

  4. ThreadPoolExecutor状态

    4.1 AtomicInteger ctl (1.高三位标识线程池当前状态 2.低29位表示工作线程个数)

    4.2 RUNNING(正常执行、处理任务)

    SHUTDOWN(不接受新任务)

    STOP(不接受任务、中断正在执行的任务、不处理队列任务)

    TIDYING(过渡状态,需要重写terminated方法)

    TERMINATED 销毁状态

    shutdown()与shutdownNow()的区别:shutdown调用后,线程池不再接收新任务,将正在执行的以及排队的执行完;shutdownNow调用后,会直接进入过渡状态(啥也不干了)等待下一步被销毁。

  5. execute()

    addWorker(command)

    workQueue.offer(command)

    addWorker(null, false) 为了处理工作线程数为0,但队列中仍有任务排队;怎么处理的

  6. addWorker() 添加工作线程

    6.1先判断线程池状态及工作线程数量

    只要不是RUNNING状态,就不处理新任务;

    如果是SHOTDOWN并且addWorker(null, false),

    6.2添加并启动工作线程

    加锁后进行状态判断(防止其他线程showdown)

  7. Worker对象( extends AbstractQueuedSynchronizer implements Runnable)

    AbstractQueuedSynchronizer 线程中断

    Runnable 存储执行的任务

    thread 绑定工作线程

    Runnable firstTask 需要执行的任务(execute传入的runnable对象)

    //中断线程不是立即让线程停止,而是给thread的中断标识设置为true

  8. runWorker() 执行任务(当前工作线程来执行)

    8.1获取传入的runnable对象并执行

    8.2runnable对象为空,从工作队列里取一个并执行

  9. getTask() 从工作队列中获取任务

    SHUTDOWN或STOP且workqueue为空,干掉当前线程

    干掉超时的非工作线程、

    阻塞一段时间从workqueue中获取工作线程(没拿到的话重新开始)

  10. processWorkerExit()

    钩子函数抛出异常才会减去工作线程数量

    加锁->统计已执行的任务数量 -> 移除任务