并发编程
123123
介绍
PV=page view
TPS=transactions per second
QPS=queries per second
RPS=requests per second
RPS=并发数/平均响应时间
配置方案
高并发、任务时间短
CPU 核数 +1,减少线程上下文的切换
并发不高、任务时间长
IO密集型 加大线程数
计算密集型 减少线程数 降低上线文切换
高并发、任务时间长
考虑缓存
增加处理能力(增加计算机)
线程配置考虑(上面)
拆分业务、解耦业务
CAS
ABA 用 AtomicStampedReference 解决 增加version
循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
只能保证一个共享变量的原子操作。AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
Volatile
内存的可见性;(主存,工作内存)
被 volatile 关键字修饰的变量,当线程要对这个变量执行的写操作,都不会写入本地缓存,而是直接刷入主内存中。当线程读取被 volatile 关键字修饰的变量时,也是直接从主内存中读取。(简单的说,一个线程修改的状态对另一个线程是可见的)。注意:volatile 不能保证原子性。
禁止指令重排序优化
有volatile修饰的变量,赋值后多执行了一个 “load addl $0x0, (%esp)” 操作,这个操作相当于一个内存屏障,保证指令重排序时不会把后面的指令重排序到内存屏障之前的位置
1
AQS (AbstractQueuedSynchronizer)
独占锁
包含:
ReentrantLock 和 ReentrantReadWriteLock.Writelock 是独占锁
获取锁的过程
tryAcquire() ? : (addWaiter()-> Node push 等待队列)
共享锁
包含:
ReentrantReadWriteLock.ReadLock,CyclicBarrier 任务栅栏(集合点),CountDownLatch和Semaphore都是共享锁
获取锁的过程
线程池
ThreadPoolExecutor
CachedThreadPool
Interger. MAX_VALUE
FixedThreadPool
定长线程池 最优数量:Runtime.getRuntime().availableProcessors()
不会释放线程
SingleThreadExecutor
同事只有一个线程,按照指定顺序(FIFO, LIFO, 优先级)执行。
ScheduleThreadPool
ScheduledThreadPoolExecutor
WorkStealingPool
Fork/Join
ThreadLocal
使用场景
数据中间件开发,用来解决数据库连接、Session管理等
分布式锁
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
线程副本
实现原理
ThreadLocalMap Entry WeekReference 弱引用
Semaphore 信号量
使用场景
食堂有5个窗口
比较
无法控制速率
CountDownLatch
是什么
这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
如何工作
使用场景
实现最大的并行性 类似 Semaphore 信号量
开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
ReentrantLock
需要在 finally 释放锁
优势
公平锁&非公平锁
tryLock(5, TimeUnit.SECONDS) //尝试等待5s锁中断
lock.lockInterruptibly();
Synchronized
公平锁
比较
Atomic (一个数量级) -> ReentrantLock 4~5倍 -> Synchronized -> (慢10~20%) Semaphore
AbstractQueuedSynchronizer AQS
提供一个状态值,getState(), setState(), compareAndSetState(),实现类可根据状态值来决定是否阻塞当前线程,或者唤醒一个正在等待的线程。
对外提供acquire(), acquireShared() 来获取状态,如果获取失败,当前线程将被阻塞。
对子类提供tryAcquire(), tryAcquireShared()模板方法来决定是否获取到了状态,子类应该根据状态值来决定返回。这个方法也要求是线程安全的。
对外提供release(), releaseShared()来释放状态, 释放状态时,会唤醒调用acquire(), acquireShared()时阻塞的线程。
对子类提供tryRelease(), tryReleaseShared()方法,来判断是否释放成功,tryRelease()返回true,或者tryReleaseShared()返回大于0的值时,即代表释放成功。
有了这些操作,我们就可以很方便的自己来实现ReentrantLock ,Semaphore, CountDownLatch这些类了,读者可以先自己思考一下这些类的实现思路,也可以直接阅读源码看下大神是怎么做的。
Linux 环境下如何查找哪个线程使用 CPU 最长
获取项目的pid,jps或者ps -ef | grep java
top -H -p pid,顺序不能改变
这样就可以打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操作系统原生线程的线程号。打出来的LWP是十进制的,"jps pid"打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。使用"top -H -p pid"+"jps pid"可以很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的原因,一般是因为不当的代码操作导致了死循环。作者:Java旅行者链接:https://www.jianshu.com/p/ac6b889e73c5來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Thread.sleep(0) 的作用是什么(要弄懂)
由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。作者:Java旅行者链接:https://www.jianshu.com/p/ac6b889e73c5來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
高并发、任务时间短
CPU 核数 +1,减少线程上下文的切换
并发不高、任务时间长
IO密集型 加大线程数
计算密集型 减少线程数 降低上线文切换
高并发、任务时间长
考虑缓存
增加处理能力(增加计算机)
线程配置考虑(上面)
拆分业务、解耦业务
Fork/Join 框架的理解
http://www.infoq.com/cn/articles/fork-join-introductionFork/Join
框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork 就是把一个大任务切分为若干子任务并行的执行,Join 就是合并这些子任务的执行结果,最后得到这个大任务的结果。Fork/Join 的核心是 work-stealing 算法。Fork/Join 使用两个类来完成以上两件事情:作者:Java旅行者链接:https://www.jianshu.com/p/ac6b889e73c5來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Last updated