摩尔定律:是由英特尔(Intel)创始人之一戈登·摩尔(Gordon Moore)提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。
Amdahl定律:系统中对某一部件采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。阿姆达尔定律实际上定义了采取增强(加速)某部分功能处理的措施后可获得的性能改进或执行时间的加速比。简单来说是通过更快的处理器来获得加速是由慢的系统组件所限制。
计算机的运算速度与他的存储和通信子系统速度差距太大!!!
如果不想让计算机长时间等待其他资源,就需要同时处理多任务
基于高速缓存可以很好的解决处理器与内存之间的速度矛盾,但是在多处理器系统中,每个处理器都有自己的高速缓存,而且他们又共享同一个主内存,
问题:当多个处理器的运算任务都涉及同一块儿主内存区域就会带来数据不一致的问题,也就是缓存一致性的问题!
解决:需要一种协议可以保障数据的一致性,如MSI、MESI、MOSI及Dragon Protocol等。
问题:速度问题导致的处理器内部运算单元没有被充分利用
解决:处理器会对输入代码进行乱序执行,计算之后对乱序执行的代码进行结果重组。
JMM的出现,能够屏蔽掉各种硬件和操作系统的内存访问差异,实现平台一致性,是的Java程序能够“一次编写,到处运行”。
内存模型定义了虚拟机将变量存储到内存并且从内存取出的规则。此处的变量是:实例字段,静态字段和构成数组对象的元素
不包括局部变量和方法参数,因为他们是线程私有的。
java内存模型规定了所有的变量都存储在主内存,每条线程还有自己的工作内存。
!线程的工作内存保存了被该线程使用的变量的主内存副本拷贝。
!线程对变量的所有操作都在工作内存中进行。
!不同线程无法直接访问对方的工作内存中的变量。
!线程之间的变量值传递需要通过主内存来完成。
如果结合之前了解的虚拟机内存组成,主内存主要对应于java堆中对象实例数据部分,工作内存对应于虚拟机占中的部分区域
主内存与工作内存之间的交互需要经历下面8种操作
1、lock锁定 | 作用于主内存的变量 | 把一个变量表示为一条线程独占的状态 |
2、read读取 | 作用于主内存的变量 | 把一个变量的值从主内存传输到线程的工作内存中 |
3、load载入 | 作用于工作内存的变量 | 它把read操作从主内存中得到的变量值放入工作内存的变量副本中 |
4、use使用 | 作用于工作内存的变量 | 把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 |
5、assign赋值 | 作用于工作内存的变量 | 把一个从执行引擎获得值付给工作内存的变量 |
6、store存储 | 作用于工作内存的变量 | 将工作内存的变量值传递到主内存 |
7、write写入 | 作用于主内存的变量 | 将值写入主内存变量中 |
8、unlock解锁 | 作用于主内存的变量 | 把一个处于锁定的变量释放,之后其他线程才能锁定它 |
一些强制规定:
volatile保证变量的可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,这个新值对于其他线程来说是立即可见的。
屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,下文有详细的分析。
当不满足下列两条时
1当在运算结果并不用来变量的当前值得时候或者能够保证只有一个单一线程修改变量的值得时候
2变量不需要参与其他状态变量共同参与的不变约束的时候
就需要加锁(使用synchronized、lock或者java.util.concurrent中的Atomic原子类)来保证并发中的原子性。
java内存模型是围绕着并发过程中如何处理原子性可见性和有序性这三个特城来建立的。
原子性:除了long和double不具备原子性协议之外,基本数据类型的访问读写都是具备原子性的
在java代码中synchronize块儿之间的操作也具备原子性
可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
三个关键字可以实现可见性 volatile synchronized final
有序性:java语言提供了volatile和synchronize关键字来保证线程之间操作的有序性。
volatile禁止了指令重排序的语义,synchronize使得一个变量在同一时刻只允许一条线程对其进行lock操作。
他判断数据之间是否存在竞争、线程是否安全的主要与依据。
结论:时间先后顺序与先行发生原则之间基本没有太大的关系,衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准
线程是比进程更轻量级的调度执行单位
线程的实现:每一个执行start方法且还未结束的thread类的实例就代表了一个线程,Thread类关键方法都是native的,一个native方法意味着这个方法没有使用或者无法使用平台无关的手段实现,也可能是为了执行效率。
实现线程的三种方式:使用内核线程实现,使用用户线程实现,使用用户线程加轻量级进程混合实现。
内核线程KLT:直接由操作系统内核支持的线程,由内核调度,负责将线程的任务映射到各个处理器上,每个内核线程都可以视为一个内核的分身。程序一般不会去执行内核线程,而是使用内核线程的高级接口,轻量级进程(LWP),其实每一种内核线程都是一一对应一个轻量级进程,并且都是一个独立的调度单元。即使有一个轻量级进程租塞了也不会影响整个进程工作,但是轻量级进程具有他的局限,系统调用代价相对较高,需要消耗一定的内核资源。
用户线程:完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。,用户线程的创建同步销毁都是在用户态中完成的,不需要内核的帮助,所以非常快速而且低消耗。但是缺点就是所有的创建 切换和调度的问题都需要自己解决,而且注入阻塞如何处理多处理器系统中如何将县城映射到其他的处理器上,这类问题会变得非常困难。因而用户线程实现的程序都一般比较复杂。
用户线程加轻量级进程混合:集合了上述两种特点,用户线程完全建立在用户空间中,并且操作系统提供支持轻量级进程作为用户线程的内核线程桥梁,大大降低阻塞风险。
协同是线程调度:线程的执行时间有线程本身控制,线程把自己的工作执行完了之后,主动通知系统切换到另外一个线程上。这样实现起来很简单,因为切换操作对线程自己是可知的,所以没有什么线程同步的问题。坏处也很明显就是线程执行时间不可控制,如果一个线程有问题出现了阻塞 其他线程也会跟着受影响。
抢占式线程调度:如果使用抢占式调度多线程系统,那么每个线程将由系统来分配执行时间,切换也不由线程决定。当一个线程出现问题,可以杀掉不至于导致系统崩溃。虽然java线程调度是系统自动完成的但是我们还是可以建议系统给一些线程多一点资源。java一共设置了10个级别的优先级来确保优先级越高的线程越容易被系统所选择。!!
Java中的线程的生命周期大体可分为5种状态。
并且在任意一个时间点,一个线程只能有且只有一种状态
1、新建NEW:这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象但是尚未启动。
2、运行RUNNABLE:这种情况指的是Thread类的对象调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU
3、无限期等待waiting:这种状态线程不会被分配cpu时间,要等待被其他的线程显示的唤醒。以下方法会让一个线程陷入无限期的等待:wait()\Thread.join()\LockSupport.park()。
4、限期等待TimeWait:处于这种状态的线程不会被分配CPU执行时间,不过他们无需等待被其他线程唤醒,在一定的时间就会由系统自动唤醒,以下方法会让线程进入限期等待:Thread.sleep\设置Timeout的Object.wait\设置Timeout的Thread.join、LockSupport.parkNanos\LockSupport.parkUntil。
5、阻塞状态BLOCKED:一直在等待获取到一个排他锁,这个时间将在另一个线程放弃这个锁的时候发生
6、结束Terminated 终止线程的状态