美文网首页
高并发程序的设计模式

高并发程序的设计模式

作者: 晚安多巴胺 | 来源:发表于2017-12-07 21:48 被阅读0次

    为什么需要并行
    1-业务要求
    – 并不是为了提高系统性能,而是确实在业务上需要多个执行单元。
    – 比如HTTP服务器,为每一个Socket连接新建一个处理线程
    – 让不同线程承担不同的业务工作
    – 简化任务调度

    2-性能

    使用多线程的原因是因为业务上需要一个逻辑执行的执行单元
    进程:一个应用程序或exe文件
    线程:进程执行运算的最小单位

    我们选用线程而不用进程的原因是,它开销大
    并行程序在多核CPU可以提高效率

    同步和异步

    同步和异步

    同步:就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。

    异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。(异步调用后会在另一个线程继续请求,当前线程可以让别人接用)

    并发和并行

    并发和并行

    临界区的概念

    临界区

    阻塞和非阻塞

    阻塞调用:是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回
    非阻塞调用:指在不能立刻得到结果之前,该调用不会阻塞当前线程

    阻塞状态的具体分类

    S5X16VORB9T~4K88_G2%AWP.png

    其中死锁是静态的,不占CPU
    而活锁是动态的,很难查,占CPU

    非阻塞状态的具体分类

    1、无障碍
    2、无锁
    3、无等待


    无障碍 无锁

    无等待:是无锁的基础上,要求每个线程都必须在有限的步骤内完成操作。所以也必定是无饥饿的。

    并行的两个定律

    Amdahl(阿姆达尔定律):串行比例很小的时候,加速比和CPU的个数是成正比的。


    Gustafson(古斯塔夫森定律)


    Thread的start()和run(),详见

    run不能开启线程只能在当前线程执行
    Thread.stop()不推荐使用,它会释放所有monitor
    这个方法太暴力了,可能让运行一半的操作结束,导致别的线程读取错误的数据

    suspend() 和 resume()
    阻塞时不会释放占用的锁(如果占用了的话)
    wait() 和 notify()
    阻塞时会释放占用的锁(如果占用了的话)
    wait()可以加时间参数,则等待有限的时间,自动启动线程
    第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

    第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调 用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

    wait() 和 notify() 方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。

    详见 http://blog.163.com/feng_welcome/blog/static/17177032420112246191360/

    Java的并发特性

    • 原子性
    • 有序性
    • 可见性
    • Happen-Before规则
    • 线程安全的概念

    原子性
    原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰
    i++不是原子操作 在两个线程 [2,200]

    有序性
    在并发时,程序的执行可能就会出现乱序


    一条指令的执行是可以分为很多步骤的
    – 取指 IF
    – 译码和取寄存器操作数 ID
    – 执行或者有效地址计算 EX
    – 存储器访问 MEM
    – 写回 WB

    LH3L3P1~%CR3AIZ78P2UZUH.png

    指令重排可以使流水线更加顺畅,这是一种CPU自己优化,消除气泡

    可见性

    Happen-Before规则
     程序顺序原则:一个线程内保证语义的串行性
     volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
     锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
     传递性:A先于B,B先于C,那么A必然先于C
     线程的start()方法先于它的每一个动作
     线程的所有操作先于线程的终结(Thread.join())
     线程的中断(interrupt())先于被中断线程的代码
     对象的构造函数执行结束先于finalize()方法

    系统的启动模式
    1、client模式客户端(启动快,不会去做优化)
    2、server模式(启动会慢点,做优化)

    并发容器的分析
    1集合包装
    HashMap
    hashmap是数组,里面是entry,每个entry都是链表(产生hash冲突),放到哪个槽位由hash算法实现

    Collections.synchronizedMap
    public static Map m=Collections.synchronizedMap(new HashMap());
    List
    synchronizedList
    Set
    synchronizedSet

    2ConcurrentHashmap
    segment=小hashmap,很多segment可以接受很多线程
    concurrenthashmap是一个高并发的hashmap
    里面使用trylock不会等待,不断试
    自旋等待,实在不行在lock,等待
    当容量不够时,rehash,使容量翻倍,尽量不去new元素,重用原来的元素
    位置变了,才new

    3BlolckingQueue
    blocking queue非高性能容器,一个阻塞队列,线程安全,非常好的多线程共享数据容器,(空)读取等,(满)写入等


    5~~A)FDL1THM~X(0MTXY89T.png

    只是等待准备io的时间放到了极少数的线程中,节省资源,避免大部分线程io等待造成资源浪费


    锁的优化

    锁的优化是为了在设计多线程,涉及到锁的动作时候将性能尽可能的提升

    并发的级别有阻塞和非阻塞的

    非阻塞有:无障碍、无锁、无等待
    他们的并发度是比锁高一点
    ----锁优化,还是比不过非阻塞
    1减少锁的持续时间(同步的代码缩小)2减少锁粒度


    JV57~Z(JJ8MV0G2DV5NJ7U3.png FW)HGUVXS6B(QE1$IWDQ21C.png
    F5RU{3X8XG[03{1_B671]3L.png Z34@VCYI370CCO2%AKZ3A(8.png 1{~{F0J`_NV6TK(BJ2TVQ2H.png

    前提无关代码很快完成

    }T5AZ627$`S17PWO6DNLXNO.png

    很多次请求锁,合成一个
    局部变量在线程的栈空间
    进行逃逸分析,如果不可能被其他线程访问,可以锁消除
    锁是一种悲观的

    内部进行的优化


    PM}Q9DLR@6SRI_4JX5W1NEE.png Z87PYL}F7[T_TYY0$Y)2A]S.png

    在虚拟机层面进行判断,当要判断一个线程是否持有锁的时候,只需要看这个对象的头部指向的空间是不是在线程的栈的地址空间 y有锁n没锁


    2%_{W}S%~UW0SE}WN`83KBC.png

    重量级锁,动用操作系统层面的同步方法

    先不停的trylock,看看能不能拿到锁,concurrenthashmap一痒的原理

    将线程挂起要消耗大约CPU八万个时钟周期,成本高


    都是jvm内部优化

    ThreadLocal
    为每一个线程分配一个实例

    simpledateformate是非线程安全的 PZX6{1BPIC2OH}_QL7)SPZV.png
    如果共享实例,起不到作用
    jetty

    使用对象池,不用回收,不用new,减少压力

    相关文章

      网友评论

          本文标题:高并发程序的设计模式

          本文链接:https://www.haomeiwen.com/subject/jeuvbxtx.html