Java Concurrency 基础知识

jopen 10年前

1.线程安全 
a.无状态的类是线程安全的 
b.所有状态都具有原子性的类是线程安全的 
原子性:即对该类的操作是不被打断的,即使在多线程的环境下 
如果一个类只有单个状态,推荐使用jdk中的java.util.concurrent.atomic包AtomicBoolean 
AtomicInteger 
AtomicIntegerArray 
AtomicIntegerFieldUpdater 
AtomicLong 
AtomicLongArray 
AtomicLongFieldUpdater 
AtomicMarkableReference 
AtomicReference 
AtomicReferenceArray 
AtomicReferenceFieldUpdater 
AtomicStampedReference 
这些类作为状态都具有原子性 
2.Synchronized 
a.如果一个方法用Synchronized关键字修饰,则调用该方法所用的锁,为调用方法的对象的固有锁(如反射),如果同时带有Static关键字修饰,则使用的锁为该方法所在的类的固有锁。 
固有锁:每个对象都隐性地带有一个锁,该锁为重入锁(重入锁将在后文介绍),用于synchronized关键字 

3.重入锁 
关 于重入锁的解释用书中英文的解释会更准确:But because intrinsic locks are reentrant, if a thread tries to acquire a lock that it already holds, the request succeeds. 

可能看完上面的还是有点难理解 
举一个书中的例子: 
看以下代码 
Java Concurrency 基础知识 
假设不是可重入锁,那会出现一个什么现象? 
调用super.doSomething()时发现该方法被锁住了,然后等待,但等待的锁自己正持有着,也就变成了死锁 

重入锁的实现: 
重 入锁是通过一个count和记录owner--一个持有该锁的Thread来实现的。当count值为0,认为这个锁当前没被任何线程持有。当 一个线程持有了该锁了,jvm就会把owner置为该线程,并把count置为1,如果同样锁线程再次请求该锁,那jvm就会把count+1,如果线程 退出同步块,则count-1,当count为0时,锁就释放了。 

4.线程状态的锁操作 
对于每一个涉及到多个变量的不变量,所有涉及到的变量都应该用同一个锁来进行同步 
怎么理解呢? 
看例子: 
Java Concurrency 基础知识 
在 vector中,每个方法都是用synchronized修饰的,但在上面的使用中--如果两个线程先后进入该语句块,再轮流执行 vector.contain(element),显然还是会添加两次同样的element到vector中,所以还是不能保证vector的原子性 

5.程序中的代码是可能被打乱的 
如果没有进行同步,编译器,进程,和运行环境将有可能会打乱运行的顺序。 
如文中所举例子: 
Java Concurrency 基础知识 
将有可能出现这样的状况:输出结果为0. 
这是因为执行的顺序被打乱了 

6.64位数据的读或写是非原子性的 
一般的数据类型,如int,执行 
int a = 1; 
int b = a; 
这些操作都是原子操作,但是像long,double就不是了 
jvm是允许把64位数据的读和写分开两次操作的,每次操作32位。 
因此,有些项目,我们对某些数据的实时性不会太在意,就不会定义该变量为原子变量,但如果是long或者double类型,就要小心了。很有可能某用户在读取的时候读到的是前一个数据的前32位,和后一个数据的后32位 

7.volatile关键字 
当 一个变量用volatile声明了,编译器会放一个notice到该变量的共享区,然后当执行的时候,就不会对这个变量在程序中的执行顺序进行 修改。并且volatile不会缓存在寄存器中,也不会缓存在别的进程中,因此,读volatile修饰的变量时总能返回一个最新更新的值。 

使用锁能同时保证变量的可见性和原子性,但volatile只能保证其可见性。 

什么时候不适合使用volatile? 
举一个经典的例子:i++操作,在并发环境下volatile是不能保证其正确性的 

什么时候可以用volatile? 
* 该变量的值不依赖该变量当前的值,像i++,或者你能保证该变量的更新只在单线程的环境下出现 
* 该变量不和其他的变量一起包含在一个不变量中,如上文的vector 
* 由于其他的原因,该变量不需要使用锁