美文网首页
Java并发编程之变量共享

Java并发编程之变量共享

作者: ayagg | 来源:发表于2019-02-20 04:38 被阅读0次

一 线程安全
所谓线程不安全,是指程序的行为不符合预期。而这只会发生在共享资源的操作上。因此,理解成员变量的各种修饰方法,是构造安全类的基础,下面我们来分析各种修饰。

  1. 没有任何修饰
  • 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修饰,因此其内容不可变,但其引用的可变性依旧可能出现线程安全问题

  1. 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时基本都不会正确。

  1. Atomic修饰
    AtomicInteger,AtomicLong,AtomicReference等,不仅满足volatile修饰的一切,同时满足基础操作的原子性,包括compareAndSet和getAndSet操作,但面对复杂的复合操作还是可能有线程安全问题
  2. 锁修饰
    保证锁的范围内复合操作的原子性,为了使程序高效,需要一定的优化设计,称为锁优化技术。
  3. final修饰
    不可变,被修饰的变量绝对线程安全,如果修饰的是对象引用,那么对象本身还是可变的,得进一步分析其安全性
  4. ThreadLocal修饰
    为每个线程维护单独的实例对象,绝对安全
  5. 静态初始化
    静态初始化的操作也是原子的

到此,包含了Java并发编程实战里所描述的各种方法


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

相关文章

网友评论

      本文标题:Java并发编程之变量共享

      本文链接:https://www.haomeiwen.com/subject/htqqdqtx.html