共享(shared)(多个线程访问)和可变(mutable)状态
synchronized提供同步机制
synchronized同步:独占的加锁机制
volatile同步:
显示锁同步:explicit lock
原子变量同步
非线程安全的操作:
非原子操作[cpu有哪些原子操作]
结果状态依赖前一个状态
程序原子方式的操作:
程序原子方式:代码不一定需要全部执行完毕,须保证其它程序不同时进入同一段代码(同一个环境)或者使用同一个竞争的变量(如:ActomicLong)
程序一般通过加锁来确保程序原子性操作
JAVA提供内置锁来提供原子性,synchronized(提供同步代码块),每个java对象都可以用作一个实现同步的锁,这些锁被称为内置锁或监视锁
JAVA的内置锁是一种互斥锁
锁的重入: 当一个线程尝试获取已经由自己获取的锁时,是会成功的
如子类覆盖父类的synchronized,调用super方法
同步和锁的区别:锁实现了互斥,同步=互斥+内存可见性
同步还有一个重要的是内存可见性:
我们不仅希望可以互斥操作,而且希望确保一个线程修改了对象状态之后,其它线程可以看到发生的变化
(synchronized代表同步)
一种常见的加锁约定:
将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使该对象上不会发生并发访问。
许多线程安全的类都使用了这种方式,如Vector和其他的同步集合类
活跃性和性能
应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去 以及不要使用阻塞的操作和注意循环内的操作
但获取锁和释放锁需要消耗资源和时间,不应把代码分的太细
<!-------------------------------------------------------------------------------------------------------!>
3.1 可见性
如:线程有自己的私有内存,线程将变量写到内存中,其它线程还是从自己线程内存中读取
即写线程的操作对读线程不可见 + p28(42)(重排序)
只要数据在多个线程之间共享,就需要正确的同步
3.1.2 非原子的64位操作 p29(43)
java内存模型要求:变量的读操作和写操作都必须是原子性的,(变量:任何变量?ps:查看java内存模型)
对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分解为2个32位的操作
3.1.3 加锁和可见性
为什么在访问某个共享可变变量时,需要所有线程在同一个锁上 => 这样保证 同步代码块的变量是 可见性的
这种锁:称之为同步锁 ?
(即锁释放的时候,线程会把线程中的值写入到内存中 ?)
3.1.4 volatile变量
volatile变量不会和其它内存操作进行重排序,volatile不会被缓存在寄存器或其它处理器不可见的地方。
访问volatile变量时不会执行加锁操作,不建议过度以来volatile,因为会使代码更加脆弱
仅当volatile变量可以简化代码实现以及同步策略的验证时,才应该使用它们。p31(45)
3.2 发布和逸出
发布: 使对象能够在当前作用域之外的代码中使用。
逸出: 当某个不该发布的对象被发布时(如对象构造完成之前发布对象),这种情况成为逸出
(fixme:逸出问题其实是另一种控制作用域的方法)
p33(47)这可能是sla对字段等精确作用域控制的原因
3.2.1 隐式的(构造函数中)使this引用逸出,如:构造器中创建并发布内部类,构造器中启动一个线程
3.3 线程封闭
fixme:类似ThreadLocal,局部变量
没有共享数据,每个线程使用自己的数据
3.3.1 Ad-hoc线程封闭(比较脆弱,不推荐使用)
:维护线程封闭性的职责完全由程序(这里程序指语言特性)来实现。
在volatile变量上有一种特殊的线程封闭,保持一个线程写,多个线程读,则写线程可以进行读取-修改-写入
(即把变量封闭到了写线程,fixme:这里的volatile变量如果是引用也可以??应该不可以)
3.3.2 栈封闭:线程内部自己的局部变量
3.3.3 ThreadLocal类: currentThread => ThreadDataMap,
当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免每次执行时重新分配该临时对象,即可以使用ThreadLocal
ThreadLocal变量类似全局变量,降低了代码的重用性,并在类之间引入了隐含的耦合性
3.4 不变性 immutable object
不可变对象一定是线程安全的:
当满足以下条件时,对象才是不可变的:=> sla的不可变对象
1. 对象创建以后状态就不能修改
2. 对象的所有域都是final的
3. 对象是正确创建的(对象创建期间,this引用没有逸出)
3.4.1 final
3.4.2 volatile
3.1.3 加锁和可见性
3.1.4 volatile 变量 比 synchronized 更轻量级
(中文翻译的根本看不懂,以下是自己翻译和理解)
When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable. So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block.
-- ps: cpu 的读和写是原子操作的
thread A对volatile 变量进行写入,随后thread B读取此变量的值,在A写入之前,所有值对A是可见的(即A直接访问的是(进程的)内存,不是自己线程的内存区域),在B读入之后,对B是可见的。所以什 么是内存可见性,写入一个volatile变量 类似 退出一个synchronized block,读入一个 volatile变量 类似 进入一个synchronized block .
退出一个synchronized block,即还没有释放锁,其它线程 读写不了,
进入一个synchronized block,即还没有获得所,线程读不了
总结:volatile 原理是使用了CPU读 写的原子性,并强制线程从进程内存中读取,而不是线程自己的内存。
引用: http://www.iteye.com/magazines/131
- 对引用变量和大部分基本类型变量(除long和double之外)的读写是原子的。
- 对所有声明为volatile的变量(包括long和double变量)的读写是原子的。
java.util.concurrent包中的一些类提供了一些不依赖同步机制的原子方法