线程安全
是多线程编程中需要重要关注的领域,在并发编程时会使用
锁
机制来解决多线程之间同一共享变量操作的问题,多线程操作同一共享变量不加锁时会让变量的状态不可控,这样的情况下线程的操作是不安全的。
而当多线程访问同一个类时,如果不用考虑这些线程在运行时环境下调度和交叉执行,并且不需要额外的同步与调用方法上的协调,这个类的行为状态仍然正确,那称这个类是线程安全
的。
像Servlet
就是线程安全的,请求之间相互隔离互不影响,在多线程运行时不需要做额外的同步与协调,任务与任务之间也没有交叉。Servlet
保持任务是无状态
的,所以无状态对象永远是线程安全的
。
但并不能保证所有的任务都是无状态
,在生活中无状态的事务举不胜举,文章阅读量
交通信号
电源开关
这些事务在遇到多线程时都需要使用状态去表示,如果使用无状态
来表示将会导致事务不可控甚至混乱。
原子性是指一个操作不可被中断,要么全部执行成功要么全部执行失败;关系性数据库事务的一大重要特性及为原子性
。
下面我们来讲讲并发编程中如何保证操作的原子性以及Java有那些原生的方法能保证原子性的操作。
上面的这些指令操作是底层的,可以作为扩展知识面掌握下。那么如何理解这些指令了?比如,把一个变量从主内存中复制到工作内存中就需要执行read,load操作,将工作内存同步到主内存中就需要执行store,write操作。注意的是:java内存模型只是要求上述两个操作是顺序执行的并不是连续执行的。也就是说read和load之间可以插入其他指令,store和writer可以插入其他指令。比如对主内存中的a,b进行访问就可以出现这样的操作顺序:read a,read b, load b,load a。
由原子性变量操作read,load,use,assign,store,write,可以大致认为基本数据类型的访问读写具备原子性(例外就是long和double的非原子性协定.
int a=1;
a++;
上面示例中第一个行的赋值操作是原子性的,第二行的a++
操作不是原子性的操作,会解释成a=a+1
。
上面例子中的a++
不是原子操作,在并发编程时就会导致状态不一致。
A
与B
线程同时对变量a
作a++
操作
A
实际执行的步骤为:
a
变量的值a+1
表达式的值计算a+1
表达式计算的值赋值给a
B
执行的步骤与A
是一样的,设A
与B
同时开始执行a+1
的操作,同时执行了第一步获取变量a
的值,同时执行a+1
表达式的计算,同时将表达式的值赋值给变量a
最后的结果a
变量的值只加了1;但预期值应该是加两次1。
针对上面的问题可以使用下面的示例代码来保证多线程执行多个表达式的原子操作:
int a=0;
synchronized{
a++;
System.out.println(a)
}
synchronized
块代码实际是在进行代码块之前获取一个锁操作,在获取锁成功后再执行代码块中的表达式,执行完成后自动释放锁,通过线程锁机制来保证代码块中的表达式操作的原子性。
在Jdk包java.util.concurrent.atomic
下提供了基本类型与引用类型操作的原子类型以Atomic
开头,操作Integer类型可以使用ActomicInteger
,操作Long
类型可以使用ActomicLong
, 操作引用类型可以使用AtomicReference
;使用原子类来执行a++
的表达式时可以这样:
AciomicInteger ai=new AciomicInteger(1);
System.out.println(ai.incrementAndGet());
在方法incrementAndGet
会对ai
变量的值加1后结果返回值,在内部保证了操作的原子性
原子类相比于synchronized
关键字做a++
这样的语义表达时更加简洁,但对于复杂的表达式ActomicInteger
无法表达,在原子类与关键字synchronized
的选择上需要看实际的情况,只是对值类型做简单的操作可以使用原子类,需要做复杂的数学计算时选择synchronized
将会更加方便。
精彩推荐:
包邮赠书!《阿里巴巴Java开发手册 第二版》
知识点:Java sychronized 内部锁实现原理
知识点: Java FutureTask 使用详解
Java ClassLoader详解双亲委派的实现原理