1.原子操作
原子操作时不能被线程机制中断的操作;一旦操作开始,那么一定可以在可能发生的“上下文切换”之前(切换到其它线程执行)执行完毕。
一般情况下不能用原代替同步,除非你是并发专家。否则就是在玩火。
“如果你可以编写用于现代未处理器的高性能JVM,那么就有资格去考虑是否可以避免同步”。
了解原子性是很有用的,原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。
2.volatile关键字
volatile关键字确保了应用中的可视性。如果将一个域申明为volatile的,那么只有对这个域产生了写操作,那么所有的读操作就斗可以看到这个修改。即使使用了本地缓存,情况也确实如此,volatile域会立即被写到主存中,而读取操作就发生在主存中。
如果一个域完全用sychronized方法或语句块来防护,那么就不必将其设置为volatile。
一个任务所作的任何写操作对这个任务说都是可视的,因此如果它需要在这个任务内部可视,那么就不需要将其设置为volatile。
档一个域的值依赖于它之前的值时,volatile就无法工作。如果某个域的值受到其它域的值的限制,那么volatile也无法工作
使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。
第一选择是synchronized关键字,这是最安全的方式。
例子1:
package
jiangning.c21;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
public
class
AtomicityTest
implements
Runnable {
private
int
i
= 0;
public
int
getValue(){
return
i
;
}
private
synchronized
void
evenIncrement(){
i
++;
i
++;
}
public
void
run() {
while
(
true
){
evenIncrement();
}
}
public
static
void
main(String[] args) {
ExecutorService exec = Executors. newCachedThreadPool();
AtomicityTest at =
new
AtomicityTest();
exec.execute(at);
while
(
true
){
int
val = at.getValue();
if
(val %2 != 0){
System.
out
.println(
"val = "
+ val);
System. exit(0);
}
}
}
}
/**
val
=
9
*/
分析:尽管return确实是原子性操作,但是缺少同步使得其数值可以在处于不稳定的中间状态被读取。此外由于i也不是volatile的,因此还存在可视性问题。
例子2.
package
jiangning.c21;
public
class
SerialNumberGenerator {
private
static
volatile
int
serilNumber
= 0;
public
static
int
nextSerialNumber(){
return
serilNumber
++;
}
}
package
jiangning.c21;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
import
java.util.concurrent.TimeUnit;
class
CircularSet{
private
int
[]
array
;
private
int
len
;
private
int
index
= 0;
public
CircularSet(
int
size){
array
=
new
int
[size];
len
= size;
for
(
int
i=0; i<size; i++){
array
[i] = -1;
}
}
public
synchronized
void
add(
int
i){
array
[
index
] = i;
index
= ++
index
%
len
;
}
public
synchronized
boolean
contains(
int
val){
for
(
int
i=0; i<
len
; i++){
if
(
array
[i] == val){
return
true
;
}
}
return
false
;
}
}
public
class
SerialNumberCheck {
private
static
final
int
SIZE
= 10;
private
static
CircularSet
serials
=
new
CircularSet(1000);
private
static
ExecutorService
exec
= Executors.newCachedThreadPool();
static
class
ServialCheck
implements
Runnable{
public
void
run(){
while
(
true
){
int
serial = SerialNumberGenerator.nextSerialNumber();
if
(
serials
.contains(serial)){
System.
out
.println(
"Duplicate: "
+ serial);
System. exit(0);
}
serials
.add(serial);
}
}
}
public
static
void
main(String[] args)
throws
Exception {
for
(
int
i=0; i <
SIZE
; i++){
exec
.execute(
new
ServialCheck());
}
if
(args.
length
> 0) {
TimeUnit.
SECONDS
.sleep(
new
Integer(args[0]));
System.
out
.println(
"No duplicates detected"
);
System. exit(0);
}
}
}
这个例子说明最安全的方法是遵循Brian的同步规则
总结:本节告诉我们在进行同步的时候要使用synchronized关键字,而不要依赖原子性,因为你不专家。