并发编程

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都是共享锁

    • 获取锁的过程

线程池

ThreadLocal

  • 使用场景

  • 线程副本

  • 实现原理

    • ThreadLocalMap Entry WeekReference 弱引用

Semaphore 信号量

CountDownLatch

  • 是什么

    • 这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。

  • 如何工作

  • 使用场景

    • 实现最大的并行性 类似 Semaphore 信号量

    • 开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。

    • 死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。

ReentrantLock

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/ac6b889e73c5arrow-up-right來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Thread.sleep(0) 的作用是什么(要弄懂)

由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。作者:Java旅行者链接:https://www.jianshu.com/p/ac6b889e73c5arrow-up-right來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

高并发、任务时间短

  • CPU 核数 +1,减少线程上下文的切换

并发不高、任务时间长

  • IO密集型 加大线程数

  • 计算密集型 减少线程数 降低上线文切换

高并发、任务时间长

  • 考虑缓存

  • 增加处理能力(增加计算机)

  • 线程配置考虑(上面)

  • 拆分业务、解耦业务

Fork/Join 框架的理解

http://www.infoq.com/cn/articles/fork-join-introductionFork/Joinarrow-up-right

框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork 就是把一个大任务切分为若干子任务并行的执行,Join 就是合并这些子任务的执行结果,最后得到这个大任务的结果。Fork/Join 的核心是 work-stealing 算法。Fork/Join 使用两个类来完成以上两件事情:作者:Java旅行者链接:https://www.jianshu.com/p/ac6b889e73c5arrow-up-right來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Last updated