并发-对象的共享


非原子的64操作

  • 非volatile类型的64位数值变量(double和long),JVM允许将64位的读或写操作分解为两个32位操作,因此在多线程中使用共享且可变的long与double类型的变量是不安全的,需要使用volatile声明或者用锁保护起来

volatile变量

  • 保证可见性且防止重排序

发布与逸出

发布: 使对象能够在当前作用域之外的代码中使用

  • 不安全的发布
/**
 * 私有的数组states已经逸出了它所在的作用域,致使调用者可以修改这个数组的内容
 */
class UnsafeStates{
    private String[] states = new String[]{"A","B"};
    public String[] getStates(){ return states; }
}
  • this引用逸出
public class ThisEscape{
    public ThisEscape(EventSource source){
        source.registerListener(
            new EventListener(Event e){
                //this对象(ThisEscape)逸出,仅当ThisEscape构造函数返回时this对象才算完整,
                //此处发布不完整的this对象(对象可能没有构造完成,就共享给其他线程使用)
                doSomething(e);
            }
        );
    }
    void doSomething(Event e){}
}
  • 使用工厂方法防止this引用在构造过程中逸出
/**
 * 使用工厂方法防止this引用在构造过程中逸出
 */
public class SafeListener{
    //必须用final修饰
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e){
                doSomething(e);
            }
        };
    }
    void doSomething(Event e){}

    //工厂方法
    public static SafeListener newInstance(){
        //先创建完整的对象(this)
        SafeListener safe = new SafeListener();
        //再将(this)发布使用
        source.registerListener(safe.listener);
        return safe;
    }
}

线程封闭

多线程间不共享数据,就不需要同步,称为线程封闭

  1. Ad-hoc线程封闭

    完全由实现着控制的线程封闭,非常脆弱,没有任何一种语言特性能将对象封闭到目标线程上

  2. 栈封闭

    局部变量就为栈封闭,局部变量是不被多个线程共享的,局部变量封闭在执行线程中,位于执行线程的栈中,其他线程无法访问,优于Ad-hoc封闭

  3. ThreadLocal类

    使用ThreadLocal类可以实现线程封闭,ThredLocal使得各个线程都能够保持各自独立的一个对象,每个线程都会重新创建一个对象与线程绑定(以空间换时间),而线程是否安全取决于初始化方法中你如何创建该对象.(使用ThreadLocal可以达到线程内全局变量效果)

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
        //重写初始化方法(为每个线程创建单独对象进行绑定时调用,
        //是否线程安全(达到线程封闭效果)取决于初始化方法如何创建对象)
        @Override
        protected Connection initialValue() {
            //返回一个新的数据库链接(Connection)对象
            return DriverManager.getConnection(DB_URL);
        }
    };
    //获取连接对象,在多线程中使用同一个对象调用该方法,
    //则会每个线程获取一个独立的Connection对象与线程绑定(K,V)形式绑定
    public static Connection getConnection(){
        return connectionHolder.get();
    }

不可变

  1. 不可变对象一定是线程安全的
    • 对象创建后其状态就不能修改
    • 对象的所有域是final类型
    • 对象是正确创建的(创建期间,this引用没有逸出)

安全发布

  • 不安全的发布
    //不安全的发布(由于可能指令重排序,不能保证发布时对象的完整性)
    //1.可使用final修饰解决,参考final内存语义
    //2.或者保证Holder类不可变,也可以安全发布
    public Holder holder;
    public void initialize(){
        //创建对象时,可能存在指令重排序
        holder = new Holder(42);
    }
public class Holder {
    //问题并不在本类,但是解决不安全发布导致的问题的另一种方式就是该类对象不可变,
    //因此使用final修饰n即可,(参考final内存语义)
    private int n;
    public Holder(int n) {
        this.n = n;
    }

    public void assertSanity() {
        if (n != n)
            throw new AssertionError("This statement is false.");
    }
}
  • 安全发布的常用模式
    • 在静态初始化函数中初始化一个对象引用(JVM在类初始化阶段创建,JVM内部存在着同步机制,由jvm保证安全发布)
    • 将对象的引用保存到volatile类型的域或者AtomicReferance对象中(可见性,原子性)
    • 将对象引用保存到正确构造对象的final类型域中(参考final内存语义)
    • 对象的引用保存到一个由锁保护的域中(同步处理,如同步容器)
  • 并发程序中使用共享对象,可用的一些策略
    • 线程封闭: 对象被封闭在线程内,只能由这个线程修改
    • 只读共享: 共享的只读对象包含不可变对象,和事实不可变对象
    • 线程安全共享: 线程安全的对象在其内部实现同步
    • 保护对象: 被保护的对象只能通过持有特定的锁来访问

文章作者: Bryson
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bryson !
评论
 上一篇
并发-对象的组合 并发-对象的组合
实例封闭 将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁 java类库中有很多线程封闭示例: Collections.synchronizedList及其类似方法,这些工厂方法通过
2019-08-18
下一篇 
数据压缩 数据压缩
数据压缩 在此主要介绍使用Huffman编码压缩和LZW压缩算法 地址: 整合为JUI程序(Java 8+) 1.Huffman压缩(霍夫曼压缩)1.1.BitBuffer辅助类 - 按bit位处理字节数组/** * BitterBuf
2019-07-30
  目录