不一样的方式实现线程安全

672 阅读2分钟

实现线程安全的方式有很多,常见的就是使用锁的机制来保证同一时刻只有一个线程访问共享变量来实现线程安全,然而锁带来的效率问题也是显而易见的,本文介绍一些不使用锁的机制来实现线程安全的方式

使用不可变类维护可变变量

class ImmutableClass {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    ImmutableClass(BigInteger lastNumber, BigInteger[] lastFactors) {
        this.lastNumber = lastNumber;
        this.lastFactors = Arrays.copyOf(lastFactors, lastFactors.length);
    }

    public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i)) {
            return null;
        }
        return Arrays.copyOf(lastFactors, lastFactors.length);
    }
}

class ThreadSafe {
    private volatile ImmutableClass immutableClass = new ImmutableClass(null, null);

    public BigInteger[] doService(BigInteger integer) {
        BigInteger[] factors = immutableClass.getFactors(integer);
        if (factors == null) {
            factors = calFactor(integer);
            immutableClass = new ImmutableClass(integer, factors);
        }
        return factors;
    }
}

使用此种方法需要注意的是:

  1. ImmutableClass的成员变量都是final的
  2. ImmutableClass的构造函数及getFactors方法返回的BigInteger[]都是经过拷贝的
  3. ThreadSafe中对ImmutableClass的引用是volitate的

正确发布对象

此处要求ImmutableClass是正确被发布的,正确发布一个对象有以下几种方式:

  1. 在静态初始化函数中初始化一个对象引用,其被正确发布是由JVM在类的初始化过程中内部的同步机制保证的
  2. 将对象的引用使用volatile修饰或保存到AtomicReference中
  3. 将对象的引用保存到正确发布的对象的final域中,其被正确发布是由final保证的,final保证对象的引用可以被其他线程看到时,该对象一定是被完全构建的,而不是处于构建过程中
  4. 将对象的引用保存到一个由锁保护的域中,如:SynchronizadMap,HashTable,线程安全库中的容器类提供了保证

上述这种方式通过返回深拷贝的对象从而保证线程安全,然而该方式存在效率问题,如果需要拷贝的内容是大量的话则会导致效率下降

基于委托的线程安全实现

class Scratch {
    private final ConcurrentHashMap<String, Point> concurrentHashMap;
    private final Map<String, Point> unmodifiableMap;

    public Scratch(Map<String, Point> map) {
        this.concurrentHashMap = new ConcurrentHashMap<>(map);
        this.unmodifiableMap = Collections.unmodifiableMap(concurrentHashMap);
    }

    public Map<String, Point> getPoints() {
        return unmodifiableMap;
    }

    public Point getPoint(String id) {
        return concurrentHashMap.get(id);
    }
    
    public void uptPoint(String id, Point point) {
        concurrentHashMap.replace(id, point);
    }

    class Point {
        private final int x;
        private final int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

此事例中Point类是线程安全的(不可变),获取位置时返回unmodifiableMap从而保证返回的map只读,而当需要修改和获取指定id的坐标时,通过concurrentHashMap保证线程安全性。

注:Collections.unmodifiableMap(Map map) 方法返回的是map的不可修改的视图,其值仍然是底层的map,因而当底层map的值发生变化时会通过unmodifiableMap反应出来,unmodifiableMap仅仅是保证不能通过其来操作底层map数据