一 线程安全
所谓线程不安全,是指程序的行为不符合预期。而这只会发生在共享资源的操作上。因此,理解成员变量的各种修饰方法,是构造安全类的基础,下面我们来分析各种修饰。
- 没有任何修饰
- int,Integer等基础数据类型
赋值和读取操作可以满足原子性,不满足可见性并且会参与重排序优化 - 对象引用
赋值和读取操作可以满足原子性,不满足可见性并且会参与重排序优化 - long,double等
赋值不能保证原子性 - What operations in Java are considered atomic?
public final class Counter {
// private int count=0;
private AtomicInteger count;
public Counter(int i) {
count = new AtomicInteger(i);
}
public void addOne() {
// count++;
// count.getAndIncrement();
}
public int getCount() {
// return count;
return count.get();
}
}
public class Ref_Safe_M_Self_S {
Counter counter = new Counter(0);
private Set<Integer> set = new HashSet<>();
public Set getSet() {
return set;
}
private static Counter[] counters = new Counter[4];
static {
counters[0] = new Counter(1);
counters[1] = new Counter(2);
counters[2] = new Counter(3);
counters[3] = new Counter(4);
}
public void wAr(int i) {
counter = counters[i];
//增加执行时间
for (int j = 0; j < 10; j++) {
}
set.add(counter.getCount());
}
}
//This test runner runs the method annotated with “Test” parallel in 4 threads.
@RunWith(ConcurrentTestRunner.class)
public class Test_Ref_Safe_M_Self_S{
private Ref_Safe_M_Self_S ref_safe_m_self_s = new Ref_Safe_M_Self_S();
AtomicInteger atomicInteger = new AtomicInteger(0);
@Test
public void wAndr() {
ref_safe_m_self_s.wAr(atomicInteger.getAndIncrement());
}
@After
public void testCount() {
Integer[] ints = new Integer[]{1, 2, 3, 4};
Set<Integer> set = new HashSet<>(Arrays.asList(ints));
assertEquals("set should contain 1,2,3,4", set,
ref_safe_m_self_s.getSet());
}
}
上面的代码里,counter类被final修饰,因此其内容不可变,但其引用的可变性依旧可能出现线程安全问题
- volatile修饰
int,Integer等基础数据类型,对象引用,long,double等,均满足赋值原子性,满足可见性并且不会参与重排序
public class Apple {
public int a = 0;
public Apple() {
for (int i = 0; i < 50000; i++) {
a = i;
}
}
}
public class SimpleCounter {
Apple apple;
// volatile Apple apple;
public void setApple() {
apple = new Apple();
}
public Apple getApple() {
while (true) {
if (null != apple) {
return apple;
}
}
}
}
@RunWith(ConcurrentTestRunner.class)
public class TestSimpleCounter {
private SimpleCounter simpleCounter = new SimpleCounter();
@Test
public void testSetApple() {
simpleCounter.setApple();
}
@Test
public void testGetApple() {
System.out.println(simpleCounter.getApple().a);
}
}
上面的代码里,当Apple构造函数里的循环足够小,在我的机器上<20000时基本都是正确的,可以满足可见性的,但超过50000时基本都不会正确。
- Atomic修饰
AtomicInteger,AtomicLong,AtomicReference等,不仅满足volatile修饰的一切,同时满足基础操作的原子性,包括compareAndSet和getAndSet操作,但面对复杂的复合操作还是可能有线程安全问题 - 锁修饰
保证锁的范围内复合操作的原子性,为了使程序高效,需要一定的优化设计,称为锁优化技术。 - final修饰
不可变,被修饰的变量绝对线程安全,如果修饰的是对象引用,那么对象本身还是可变的,得进一步分析其安全性 - ThreadLocal修饰
为每个线程维护单独的实例对象,绝对安全 - 静态初始化
静态初始化的操作也是原子的
到此,包含了Java并发编程实战里所描述的各种方法

- 其他
无修饰还有一种情况是引用类型的成员变量如果没有状态,或者不可修改,即不可变类,则也是绝对安全的,譬如Spring里的Service
网友评论