java并发编程实践之线程安全性

1.首先了解什么是线程安全性?

线程安全性定义:当多个线程访问某个类时,不管运行环境采用何种调度方式和线程如何交替轮流执行,并且在主代码中不需要任何额外的同步或协同,这个类都能表现出正确性的行为,那么就称这个类是线程安全的。

大致意思就是多个线程要访问同一个类,不管操作系统如何调度这些线程,以及线程如何争抢cpu,我都不需要写额外的同步或者协同代码,该类都能正确的被执行;(尼玛,书上对正确性的行为解释的太反人类了)。

还有类的正确性行为:类里面的每个变量都有一个取值范围,比如int类型,从int_MIN到int_Max,还有变量和变量之间也有一些条件,比如low

编写线程安全代码的核心

要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是可变的共享变量。对象的状态是存储在状态变量(如静态域,实例变量)数据。

对象状态: java并发编程实践第4章有解释到,对象状态就是对象的类变量,实例变量其他类中域的引用组成的n元组的所有取值,比如对象有两个变量,每个变量有2种取值,那么该对象的对象状态共有2*2种;这里的变量不包括方法里面的局部变量,因为方法的局部变量被单独存在调用该方法的虚拟机栈里面,而虚拟机栈是线程私有的,不涉及共享问题。

2.对于并发来讲有两个关键词:共享,可变

 共享 :说明该对象可以被多个线程同时访问;

 可变: 变量的值在其生命周期内是发生变化的,如果一个对象所有的变量值都不发生变化,即该对象的对象状态不发生变化,那这个对象只要正确发布了,不用添加任何同步手段,都是线程安全的。

示例:一个无状态的Servlet一定是线程安全的
java并发编程实践之线程安全性_第1张图片
总结:就是一个类如果没有类变量,实例变量和对其他类中域的引用,那么该类被正确发布以后就是线程安全的。


3.

竞态条件: 在并发编程中,由于不恰当的执行顺序而导致不正确的执行结果。


最常见的就是先检查后执行(check-then-act),即通过一个可能失效的观测结果去决定做下一步事情。
1.if(A)
2.  then B

如果A正确,那么执行B,可是如果在语句1,2之间条件A变成了false,那么这种情况是不允许的,就被称为竞态条件。之所以发生这种情况是由于语句1.2的操作是复合操作,而不是原子操作。要避免竞态条件就是一个线程修改该变量时,其他线程不能读写该变量,只有在该变量被改写前或者改写后才能被其他线程使用。


4.重入


对于对象A,如果线程B已经拿到了A的内置锁,线程C再请求A的内置锁,那么线程C就会被阻塞,直到B释放A的内置锁为止;如果线程B再向A请求内置锁的话就能成功,原因在于内置锁是可重入的。用官方话说就是:如果某个线程试图获得一个已经由它持有的锁,那么这个请求就会成功。代码如下:
java并发编程实践之线程安全性_第2张图片

线程访问LoggingWidget的doSomething时会访问到super.doSomething方法,线程两次获得LoggingWidget实例对象的锁。这个地方以前有疑问,为什么线程不是获得子类对象的锁和父类对象的锁,看了java子类内存模型后解决该问题。对java来说,实例化子类对象时,也会同时实例化父类对象,就java堆内存而言,父类对象是在子类对象里面的,子类对象有一个super关键字,通过该关键字能够使用父类对象里面的变量和方法;其实父类对象此时更像是子类对象里面一个特殊的变量。


5.活跃于性能

在处理对象并发的时候,锁的粒度越小越好,对于计算量很大的操作,我们可以考虑将其放到同步代码之外;这样有利于提高并发编程的效率;例子如下:
java并发编程实践之线程安全性_第3张图片

facators = facator(i);因式分解函数,计算量比较大,将其单独拉出来,没有放在同步代码块里面;

你可能感兴趣的:(java)