以前写的java多线程知识

JMM(java内存模型)

可见性

原子性

有序性

volatile关键字

  1. 保证可见性

  2. 禁止指令重排

    处理器在进行重排序时必须要考虑指令间的数据依赖性

    多线程由于交替执行,由于编译器优化重排的存在,两个线程使用的变量保持一致性是无法确定的,结果无法预测,通过加内存屏障实现。

    内存屏障的作用

    1. 阻止屏障两侧的指令重排序;
    2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

    对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;

    对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

  3. 不保证原子性(需要使用AtomicInteger 原子类)

  4. 创建对象的过程

    instance = new Singleton(); 
    
    分成下面3步
    memory = allocate(); // 1 分配对象内存空间
    instance(memory); // 2初始化对象
    instance = memory; // 3 设置instance指向刚分配的内存地址,此时instance != null

Calllable**接口

使用 FutureTask<> 进行连接 Runnable接口与Callable接口

多个线程争抢FutureTask,只算一次FutureTask

CAS(比较交换

CAS是一条CPU并发原语

判断内存某个位置值是否为预期值,如果是,则更改为新的值

  1. AtomicInteger 类采用CAS实现

    getAndInt(Object var1, long var2, int var4)

    var2:valueoffset(内存地址偏移量),先用var1与var2得到一个缓存值var5,然后将var1对象里的值与缓存值比较,如果一样,则更新为var4。

  2. CAS底层原理----自旋锁

    Unsafe----CAS的核心类,(native) 可以直接操作特定内存的数据,内部方法像C的指针一样操控内存

    变量valueOffset,表示该变量在内存中的偏移地址,Unsafe类根据内存偏移地址获取数据

    变量value用volatile修饰,保证多线程之间的内存可见性。

    ABA问题

    1 当值不变时,有可能过程中改变了多次,

    原子引用类,解决ABA问题

    AtomicReference()

    集合类

    ArrayList

    ConcurrentModificationException 并发修改异常

    解决方案

    1 可以用Vector代替

    2 使用Collections.synchronizedList()

    3 CopyOnWriteArrayList()

    ​ 写时复制,在每次添加的时候,使用ReentrantLock进行加 锁,然后复制整个列表

    ArraySet

    在JUC里,有CopyOnWriteSet()实现,继承了AbstractSetj类,底层是CopyOnWriteArrayList()

    HashMap

    在JUC里,有一个包:ConcurrentHashMap()类实现,通过加锁

    公平锁 非公平锁

    公平锁:多个线程按照申请锁的顺序来获取锁,类似排队

    非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程先获取锁,在高并发情况下,会造成优先级反转以及饥饿现象,非公平锁吞吐量较大。

    ReentrantLock可以指定构造函数来得到公平锁或非公平锁,默认是非公平锁

    Synchronized也是一种非公平锁

    可重入锁(递归锁)

    为了解决线程死锁问题

    同一线程外层函数获得锁之后,内层递归函数仍能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方***自动获取锁

    ReentrantLock/Synchronized就是可重入锁

    自旋锁

    尝试获取锁的线程不会立即阻塞,而实采用循环的方式获取锁,能够减小线程上下文的切换,缺点是循环会消耗CPU

    读写锁

    ReadWriteLock()

    CountDownLatch/CyclicBarrier/Semaphore

    CountDownLatch

    一个线程等待其他线程执行完毕后才执行,例如 秦灭六国,等到六国全被灭之后,才能统一,主线程使用await()方法等待。

    CyclicBarrier

    其他线程来了之后,要同时等待所有线程到了才继续执行,在线程中使用await()方法等待

    Semaphore(信号量)

    两个目的,1 用于多个共享资源的互斥使用,2 用于并发线程数的控制

    阻塞队列

    ArrayBlockingQueue

    基于数组结构的有界阻塞队列,此队列按FIFO(先进先出原则)对元素进行排序

    LinkedBlockingQueue

    基于链表结构的阻塞队列,按FIFO排序元素,吞吐量比ArrayBlockingQueue要高

    SynchronousQueue

    一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

    Synchroonized 与Lock区别

    1 原始构成:

    ​ Synchronized是关键字,属于jvm层面,底层通过monitor对象来完成,wait/notify也依赖monitor对象,

    ​ Lock类是具体类,是api层面的锁

    2 使用方法

    ​ Synchronized方法不需要用户手动释放,当synchronized代码执行完后,系统自动让线程释放对锁的占用

    3 等待是否可中断

    ​ synchronized不可中断,除非抛出异常或正常执行完成

    ​ ReentrantLock可中断,1 设置超时方法tryLock() 2 LockInterruptibly()放代码块中,调用interrupt()方法可中断

    4 加锁是否公平

    ​ synchronized 是非公平锁

    ​ ReentrantLock可设置公平与非公平

    5 condition

    ​ synchronized没有

    ​ ReentrantLock实行分组放行

    生产者消费者模式

    通过synchronized、wait、notify来实现

    通过Lock、condition(await、signal)实现

    通过BlockingQueue实现

    线程池

    优势

    1 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗

    2 提高响应速度,当任务到达,不需要等待线程创建就能立即执行

    3 提高线程的可管理性,无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

    分类

    1 Executors.newFixedThreadPool():执行长期的任务,性能好很多

    2 Executors.newSingleThreadExecutor():一个任务一个任务执行的场景

    3 Executors.newCachedThreadPool():执行很多短期异步的小程序或者负载较轻的任务。

    底层实现

    几种线程池的顶层实现为ThreadPoolExecutor()

    七大参数

    corePoolSize

    核心线程数

    maximumPoolSize

    线程池能够容纳的同时执行的最大线程数

    keepAliveTime

    多余的空闲线程的存活时间,当前线程数量超过corePoolSize时,空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程

    unit

    keepAliveTime 单位

    workQueue

    阻塞队列

    threadFactory

    线程工厂

    handler

    拒绝策略

    1 AbortPolicy(默认) 直接抛出RejectedExecutionException异常组织系统正常运行

    2 CallerRunsPolicy : ”调用者运行“一种调节机制,该策略不会抛弃任务,也不会抛出异常

    3 DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

    4 DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常

    如何配置线程池参数

    根据:CPU密集型、IO密集型

    CPU密集型任务配置尽可能少的线程数量:CPU核数 + 1

    IO密集型:1 cpu 核数 * 2 ; 2 CPU核数 / 1 - 阻塞系数, 阻塞系数在0.8-0.9之间

    死锁

    解决

    jps 命令定位进程号

    jstack找到死锁查看

全部评论

相关推荐

饿魔:去不了大厂,总有能去的地方,不知道焦虑什么
点赞 评论 收藏
分享
评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务