线程安全性
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的
public class A {
public void test(){
//....
}
}
无状态对象是线程安全的,其不包含任何域,也不包含任何对其他类中域的引用,调用过程产生的临时状态也仅存在于线程栈上的局部变量中
竞态条件
public class A {
private long count;
public long getCount() {
return count;
}
public void test() {
//....
count++;
}
}
如果新增计数器记录方法调用次数,此时类变为非线程安全,对于“读取-修改-写入”复合操作过程,两个线程执行情况可能如下
由于不同的运行顺序会导致不一样的结果,称为竞态条件
原子性操作
对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作
public class A {
private AtomicLong count;
public long getCount() {
return count.get();
}
public void test() {
//....
count.incrementAndGet();
}
}
可使用原子变量类AtomicLong代替long确保所有对count的操作是原子性
内置锁
若两个原子变量相互影响,则又可能出现竞态条件,如下count为0时,线程1和2同时新增了count,但线程1再调用getCount()已为2导致错误
public class A {
private AtomicLong count;
private AtomicBoolean isOdd;
public long getCount() {
return count.get();
}
public void test() {
//....
count.incrementAndGet();
isOdd.set(getCount() % 2 == 0);
}
}
此时需要使用如下同步代码块,其包含两个部分
- 作为锁的对象引用,每个java对象都可作为锁,称为内置锁
- 这个锁保护的代码块
synchronized(lock){
......
}
synchronized修饰方法时
- 整个方法体为同步代码块
- 锁就是方法调用所在的对象,若是static方法则以class对象作为锁
如上,可添加synchronized让两个变量的修改具有原子性
public class A {
@GuardedBy("this")
private Long count;
@GuardedBy("this")
private Boolean isOdd;
public long getCount() {
return count;
}
public synchronized void test() {
//....
count++;
isOdd = getCount() % 2 == 0;
}
}
内置锁是可重入的,即线程可以获得一个已经由它自己持有的锁(synchronized方法可互相调用),锁的颗粒度是线程而不是调用
活跃性与性能
上面用synchronized加锁方法可以保证原子性,但每次只有一个线程可以执行(即线程1调用了对象的synchronized方法,线程2想要调用其他synchronized方法时需等线程1释放锁),若方法耗时过长则会影响性能
public class A {
@GuardedBy("this")
private Long count;
@GuardedBy("this")
private Boolean isOdd;
public long getCount() {
return count;
}
public void test() {
//....
synchronized (this) {
count++;
isOdd = getCount() % 2 == 0;
}
}
}
如上,缩小锁的范围,只限制共享变量,避免其他操作同步