高并发程序的设计模式
为什么需要并行
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
指令重排可以使流水线更加顺畅,这是一种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
为每一个线程分配一个实例
如果共享实例,起不到作用
jetty
使用对象池,不用回收,不用new,减少压力