多线程带来的性能问题:
调度:上下文切换
* 什么是上下文切换
- 什么是上下文切换:当一个线程调用Thread.sleep()想进入阻塞状态,线程调度器就会阻塞这个线程,然后让另一个等待CPU资源的线程进入的Runnable状态,这个动作就是一个上下文切换。开销很大,有时比线程执行时间都长,通常一次上下文切换会消耗5000-10000个CPU时钟周期,大约是几微秒,对于CPU而言,是一个很大的开销了。
* 什么是上下文
- 什么是上下文:一次上下文切换,主要包含以下活动,首先要挂起一个线程,然后把这个线程的状态存在内存的某处,这个状态就是上下文。
* 缓存开销
- 缓存开销:缓存失效。CPU会根据算法会将很多数据缓存到CPU里面以便下次更快的再次使用。但是一旦进行了上下文切换,CPU即将执行不同线程不同代码,原来的缓存就失去了价值,所以CPU就需要重新进行缓存,这导致线程被调度后一开始的速度有点慢,是因为之前的缓存大部分都失效了。所以CPU为了防止这种情况,设置了一个最小开销时间,两次上下文切换之间,不能小于这个最小开销时间,否则因为缓存带来的开销可能大于程序本身的执行时间。
* 何时会导致密集的上下文切换
- 何时会导致密集的上下文切换:抢锁、IO等导致线程频繁的阻塞,就会导致频繁的上下文切换。
协作
- 协作: 内存同步
我们编辑器包括CPU会优化我们的程序,比如说可能会指令重排序,让我们的缓存能利用的更多些;或者JVM会优化我们的锁,发现没必要的锁会进行删除;或者内存方面,由于JMM java内存模型规定我们有主内存以及各个CPU有自己的缓存,这种情况下,使用缓存能提高程序的速度,因为不必要每次对主存进行同步,但是对于多线程,我们经常使用synchronized、volatile关键字去禁止,或让不同线程的缓存会失效,这样的话也会因为内存同步问题带来开销,因为它就没办法在自己的CPU里做进一步的缓存,只能使用主存,降低了效率。
上篇:线程安全第三大类问题——对象发布和初始化的安全问题
下篇:JVM内存结构、Java对象模型、Java内存模型(JMM)