多任务处理在现代计算机操作系统中几乎已是一项必备的功能。
在许多场景下,让计算机同时去做几件事情,不仅是计算机的运算能力强大,还有一个重要的原因是计算机的运算速度与它的存储通信子系统的速度差距太大
,大量的时间都花费在磁盘I/O、网络通信或者数据库访问上。
如果不希望处理器在大部分时间里都处于等待其他资源的空闲状态,就必须使用一些手段去把处理器的原酸能力“压榨”出来,否则就会造成很大的性能浪费,而让计算机同时处理几项任务则是最容易想到的,也被证明是非常有效的“压榨”手段。
让计算机并发执行若干个任务与更充分地利用计算机处理器的效能,并没有想象的那么简单,其中一个重要的复杂性是绝大多数的运算任务都不可能只靠处理器“计算”就能完成。处理器至少要与内存交互,如读取运算数据、存储运算结果等,这个I/O操作就是很难消除的(无法只靠寄存器来完成所有的运算任务)。
由于计算机的存储设备与处理器
的运行速度有着几个数量级的差距
,所以现代计算机都加入高速缓存
作为内存与处理器之间的缓冲:将运算需要使用的数据复制到缓存中,让 运算能快速进行,当运算结束后再从缓存同步回内存 之中,这样处理器就无需等待缓慢的内存读写了。
基于高速缓存解决很好解决了处理器与内存速度之间的矛盾,但也引入了一个新的问题:缓存一致性
,在多路处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一个主内存。当多个处理器的运算任务都涉及到同一个主内存区域时,将可能导致各自的缓存数据不一致。为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作。
除了增加告诉缓存之外,为了使处理器内部的运算任务单元尽可能被充分利用,处理器可能会对输入代码进行乱序执行优化
,处理器会在计算之后将乱序的执行的结果重组,保证该结果与顺序执行的结果是一致的,但不保证程序中的各个语句计算先后顺序与输入代码中的顺序一致,因此如果存在一个计算任务依赖另外一个计算任务的中间结果,那么其顺序并不能靠代码的先后顺序来保证。与处理器的乱序执行优化类似,Java虚拟机的即时编译器中也有指令重排序优化
。
Java内存模型的主要目的是定义程序中各种变量的访问规则,即关注虚拟机把变量值
存储到内存和从内存中去除这样的底层细节。
变量
:与Java编程中所有的变量有所区别,它包含了实例字段、静态字段和构成数组对象的元素,但是不包含局部变量和方法参数,因为后置是线程私有的,不会被共享。
Java内存模型规定了所有的变量存储在住内存中,每个线程都有自己的工作内存,线程的工作内存
保存了被该线程使用变量的主内存副本
,线程对变量的所有操作都必须在工作内存中进行,而不嗯呢该直接读写主内存中的数据。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存完成。如下图
Java内存模型中定义以下8种操作
:
Java内存模型规定了在上述8中基本操作是必须满足如下规则
:
关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制。
当一个变量被定义volatile之后,它将具备以下特征:
第一:保证此变量对所有线程的可见性,当一个线程修改这个变量的值,新值对其他所有线程来说是可以立即得知的。而普通变量的值在线程间传递需要通过主内存来完成。
注意:普通变量一般是当前线程执行完成之后才会将值回写到主内存,而volatile变量是值修改完成之后立马回写到主内存,在每次use之前都会先执行read和load操作从主内存同步数据到工作内存。
第二:禁止指令重排优化。普通的变量仅会保证在该方法的执行过程中所有依赖赋值结构的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。
由于Java里面的运算符操作并非原子性的,这导致volatile变量的运算在并发下一样是不安全的。
如:在当前线程执行到use之后,其他线程进行值同步,当前线程计算的时候还是用旧值计算。在计算完成之后assign操作时, 导致计算结果是错误的。且会将错误值进行同步到其他线程。
cas执行包含key(在内存中的地址),oldValue(预期值),value(设定值)。cas执行是原子性的获取当前值,和预期值比较,如果相等则设置设定值。线程获取锁成功。
在设定cas值得时候最好采用递增值处理,否则会出现ABA问题。即一个T1线程将原值为A的值修改为B,在一个线程T2将B修改为A。
条件互斥: 可用资源数量少于线程数。
持有并等待其他: 至少持有一个资源,并且等待获取其他资源
不可剥夺: 持有的资源只能在使用完成才能释放
环路等待: 线程相互持有对方所需要的资源
因现代计算机大部分都是多核处理器,能够让两个或两个以上的线程同时并发执行。当线程执行时需要资源被锁定,这个时候让当前线程“稍等一下”,但不放弃处理器执行时间,看持有锁的线程是否很快就释放锁,为了让线程等待,只需要执行一个忙(空)循环自旋。
虚拟机即时编译在运行时,对一些代码要求同步,但是对检查到不可能存在共享数据竞争的锁进行消除。
如果一系列连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体之内的,在这种情况下会把锁的范围扩大。