1.原子性:一个操作要么执行完,要么不执行。
2.可见性:线程修改数据以后,其他线程应该立马可见。
3.有序性(指令重排 影响多线程)
volatile的作用
1.当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值(保证可见性)。
2.保证一定的“有序性”
例如 volatile int i=10;
线程一: i++;-》》》》》取i,入栈,自增,出栈,写入主存
线程二:i++;
线程一,读取10并且入栈准备计算,中断,线程二运行修改i=11;(此时通知线程一无效,但是入栈了,线程一无反应)线程一继续运行得11写入 最终结果为11(错误!!!!)
内存间交互操作
lock 把变量标记为一个线程独占
unlock 解锁
read 从主存中读取数据到工作内存
load 把获取的变量值放入工作内存的变量副本中
use 把变量的值传递给执行引擎(入栈)
assign 作用于工作内存的变量,把从执行引擎获取的值赋给工作内存变量
store把变量值从工作内存传递给主存
write 作用于主存变量,把store传递的变量赋值给主存变量
hanppens-before规则
程序次序规则:一个线程内操作有序。
管程锁定规则:对于同一个锁,unlock先于lock
volatile规则:对于一个被修饰的变量,write先于read
线程启动规则:start()先行发生于此线程的每一个动作
线程终止规则:线程所有操作先行发生于对此线程的终止检测。join(),isAlive()
线程中断规则:对线程的interrupt()方法调用先行发生于被中断线程的代码检测到中断事件的发生
对象终结规则:初始化完成先行于finalize()的开始
传递性:
Double Check
private volatile Resource resource;
public Resource getResource(){
if (resource == null){
synchronized(this){
if (resource==null){
resource = new Resource();
}
}
}
return resource;
}
java与线程
线程的实现:1.内核线程 :通过操纵调度器来对线程进行调度,并映射到CPU
轻量级线程:内核线程的一种gao高级接口,每个LWP都有一个内核线程支持
2.用户线程UT:广义上非内核线程,狭义上线程完全建立在用户线程库上,不需要内核的帮助,优点开销小,确定没有内核支持。所有线程操作都要用户程序自己处理。
3.用户线程+LWP混合实现:java中,SUN jdk一个java线程就映射到一个LWP中 windows,linux都是
Solaris平台同时支持1:1,n:n,有参数指定
五种线程状态
New
Runable
Waiting
Timed Waiting
Blocked
Terminated
线程安全:多个线程访问同一个对象,如果不用考虑运行时环境下的调度和交替执行,也不需要额外的同步或对调用方进行任何协调操作,都可以得到正确的结果。
java中分为:不可变,final修饰,不受多线程影响
绝对线程安全,不管运行时环境如何调用者都不需要任何额外的操作同步措施
相对线程安全,对这个对象单独的操作是线程安全的
线程兼容,并不安全,可以通过同步手段保证
线程对立,无论是否采取tong同步措施,都无法在多线程环境中使用的代码
线程安全的实现方法
1.互斥同步(悲观) 费时,因为线程挂起恢复要在内核态中完成
保证共享数据在同一时刻只能被一个(或一些,使用信号量的时候)线程使用,实现互斥同步有临界区、互斥量、信号量synchronized对同一个线程是可重入的。
2.非阻塞同步(乐观)
测试并设置(Test and Set)
获取并增加(Fetch and Increment)
交换(Swap)
比较并交换(CAS) ABA,循环时间长开销大,只能保证一个共享变量的原子操作
加载链接/条件存储(Load Linked/Store-Conditional LL/SC)
3.无同步方案
可重入代码
线程本地存储,限制在一个线程中进行操作
锁优化
自旋锁与自适应自旋
共享数据的锁定状态只会持续很短的一段时间,阻塞同步开销过大,因此只需要让后面请求锁的线程“稍等一下”
自旋不能代替阻塞,避免了线程切换开销,但是要占用处理器时间,若锁被占用的时间过长,自旋线程只会浪费处理器资源
因此有一定的限度,例如自旋次数默认为10次,没有获得锁,就会挂起线程。可以通过参数修改。现在有自适应自旋。
锁消除
指jvm即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的的锁进行消除。锁
消除的主要判定依据来自于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程
访问到,那么就可以把其看做线程栈上的数据看待。
锁粗化
一系列连续加锁解锁操作都对同一个对象,甚至在循环体中,没有线程竞争也会有性能损失,因此,直接把锁覆盖到整个操作序列,这样只加锁一次就好。
偏向锁
在无竞争的情况下把整个同步过程消除掉。这个锁会偏向于第一个获得他的线程,如果该锁没有被其他线程获取,那
持有该锁的线程将永远不需要被同步。
未锁定 哈希码 分代年龄 0 01
当锁对象第一次被线程获取的时候,虚拟机把标志位设为01,即偏向模式,同时,用CAS操作把当前线程的ID记录在MarkWord中 若CAS成功,持有偏向锁的线程每次进入这个锁相关的同步块的时候,虚拟机可以不再进行任何同步操作
当另外一个线程尝试去获取这个锁的时候,偏向模式结束。 根据锁对象目前是否处于被锁定状态,撤销偏向后恢复到未锁定(01)或者轻量级锁定(00),后序同步操作按轻量级锁执行
轻量级锁
在无竞争的情况下使用CAS操作去消除同步使用的互斥量
代码进入同步块的时候,如果此同步对象没有被锁定(标志位为01)虚拟机首先将 当前线程的栈帧中建立一个名为Lock Record(锁记录)的空间,用于存储锁对象目前的Mark Word的拷贝(Displaced M W),这时候线程堆栈与对象头的状态如下所示。然后,虚拟机用CAS操作尝试将 对象 的MW更新为指向LR的 指针,
如果这个更新动作完成了,那么这个线程就拥有了该对象的锁,并且对象MW的标志位转变为“00”表示对象处于轻量级锁定状态下,此时如图所示。
如果更新失败了。虚拟机首先会检查对象的MW是否指向当前线程的栈帧,如果只说明当前线程已经拥有这个对象的锁,那么直接进入同步块继续执行
否则说明这个锁对象已经被其他线程抢占了。如果有两个以上的线程争用同一个锁,那么轻量级锁会升级为重量级锁,标志位变为(10)对象MW中存放的就是指向重量级锁的指针,后面等待锁的线程也要进入阻塞状态。
解锁操作,如果对象的MW仍然指向线程的锁记录,那就用CAS操作把对象当前的MW和线程中复制的Displaced MW替换回来,成功 则同步完成,失败则说明有其他线程尝试过获取该锁,那么在释放锁的同时,唤醒被挂起的线程