你们都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程当中,势必涉及到数据的读取和写入。因为程序运行过程当中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,因为CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU比起来要慢的多,所以若是任什么时候间对数据的操做都要经过和内存的交互来进行,会大大下降指令执行的速度。所以在CPU里面就有了高速缓存。html
也就是,当程序在运行过程当中,会将运算须要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就能够直接从它的高速缓存读取数据和向其中写入数据,当运算结束后,再将高速缓存中的数据刷新到主存当中。举个简单的例子,好比下面的这段代码:编程
i=i+1 |
当线程执行这个语句时,会先从主存当中读取i的值,而后复制一份到高速缓存当中,而后CPU执行指令对i进行加1操做,而后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。缓存
这个代码在单线程中运行是没有任何问题的,可是在多线程中运行就会有问题了。在多核CPU中,每条线程能够运行于不一样的CPU中,所以每一个线程运行时有本身的高速缓存(对单核CPU来讲,其实也会出现这种问题。只不过是以线程调度的形式来分别执行的)。本文咱们以多核CU为例。安全
下图中,线程1和线程2共享i,并同时对i进行i++操做,可能存在这种状况,最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。多线程
为了解决缓存不一致问题,一般来讲有如下2种解决方法:框架
这两种方式都是硬件层面上提供的方式。性能
在早期的CPU当中,是经过在总线加Lock#锁的形式来解决缓存不一致的问题。由于CPU和其余部件进行通讯都是经过总线来进行的,若是对总线加Lock#锁的话,也就是说阻塞了其余CPU对其余部件访问(如内存),从而使得只有一个CPU能使用这个变量的内存。好比上面例子中,若是一个线程在执行i=i+1过程当中,在总线上发出了LOCK#锁的信号,那么只有等待这段代码彻底执行完毕以后,其余CPU才能从变量i所在的内存读取变量。这样就解决了缓存不一致的问题。优化
可是上面的方式会有一个问题,因为在锁定总线期间,其余CPU没法访问内存,容易致使如下的Starvation(饥饿)和DeadLock(死锁)问题。spa
因此就出现了缓存一致性协议。最出名的就是Intel的MESI协议,MESI协议保证了每一个缓存中使用的共享变量的副本是一致的。它的核心思想是:当CPU写数据时,若是发现操做的变量是共享变量,即在其余CPU中也存在该变量的副本,会发出信号通知其余CPU将该变量的缓存行置为无效状态,所以当其余CPU须要读取这个变量时,发现本身缓存中缓存该变量的缓存行是无效的,那么它就会从内存中从新读取。线程
线程1锁定A,尔后尝试锁定B,线程2锁定B,尔后尝试锁定A,形成两个线程同时等待对方释放锁,进入死锁状态。
总结:多线程编程容易产生问题的根本缘由在于锁
不用切换至内核态,可能会更高效
不占用CPU资源
会切换至内核态
你只能在有限的一些情形下使用volatile变量替代锁。要是volatile变量提供理想的线程安全,必须同时知足下面两个条件:
实际上,这些条件代表,volatile变量不能用做线程安全计数器。虽然增量操做(x++)看起来相似一个单独操做,实际上它是一个有读取-修改-写入操做序列组成的组合操做,必须以原子方式执行,而volatile不能提供必须的原子性操做。
简单局其中使用一例:状态标志
也许实现volatile变量的规范使用仅仅是使用一个boolean状态标志,用于指示发生了一个重要的一次性时间,例如完成初始化或请求停机。
不少应用程序包含了一种控制结构,形式为"在尚未准备好中止程序时再执行一些工做"。如如下代码所示,将volatile变量做为状态标志使用:
volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { |
极可能会从循环外部调用shutdown()方法 -- 即在另外一个线程中 -- 所以,须要执行某种同步来确保正确实现shutdownRequested变量的可见性。相对于synchronized,这里很是适合使用volatile。
总结:
那么如何解决starvation的问题?
引入公平锁
参考:http://www.importnew.com/18126.html
参考:http://www.cnblogs.com/shangxiaofei/p/5564047.html
参考:炼数成金--多线程及容错性处理