JAvaマルチスレッドオブジェクトの安全なパブリケーション


 
  
简单解释下多线程中的重要概念

活跃性: 意味着正确的事情最终会发生
活跃性问题: 比如单线程中出现的无限循环

性能问题:和活跃性问题相对, 活跃性问题意味着正确的事情一定会发生,但是不够好,我们希望正确的事情尽快发生
线程安全性: 一个类,被多个线程同时调用,且多个线程不需要额外的同步机制,就能保证这个类的方法被正确执行,那么这个类就是安全的
原子性:count++就不是原子操作,它分为读取修改写入三个操作,boolean的读写是原子操作
竞态条件: 最常见的就是先检查后执行.意味着程序的正确性依赖于多线程执行时序的正确性
不变性条件:即一组操作,通过加锁的方式,保证这组操作以原子操作的方式执行
复合操作:访问一个状态的所有操作,要以原子性的方式访问, 即这组操作只能同时在同一个线程中执行,其他线程必须等待
重入: 同一个线程可以重入他获得的所有锁
重排序: 编译器和运行时和处理器可能对操作进行重排序(处于性能方面的考虑), 比如构造器中初始化对象中的变量和返回这个对象引用,这两个操作,可能出现构造器没有完全初始化,引用就被返回,.这种现象被称为不安全的发布
可见性问题:当前线程对共享变量的修改对其他线程不可见,内置锁可以保证同步代码块中的变量的更新对其他所有线程可见
发布:将一个对象暴露到多线程环境中
逸出:一个本不该被暴露到多线程环境的对象被错误的暴露到多线程环境中
构造器中的this指针逸出:不论是逸出到多线程还是当前线程,都会出错,可能读取到的是未完全初始化的变量值,比如int i,首先默认设置为0,然后在构造器中初始化为100,如果构造器中this指针逸出,那this可能读取到不正确的0值
安全地构造对象: 保证this指针不会再构造器中逸出即可,可以使用静态方法进行初始化
线程封闭: 栈封闭:使用局部变量,局部变量一定是当前线程私有的, threadLocal 类:ThreadLocal中的数据被封闭在当前线程中
对象的不变性: 1,对象被正确创建(正确创建指的是构造过程this指针没有逸出)
2,对象创建之后,状态不允许被修改
3,所有的于都是final类型的(这个不需要,只要保证实际不可修改即可,比如吧对象放到ConccurencyMap中,外围类只提供isContain()方法). 同时不可变对象一定是线程安全的

为什么会产生安全发布这个问题

出于性能方面的考虑. 编译器,运行时,处理器可能会对操作进行重排序,比如创建对象时,执行构造器初始化实例的变量的操作,和返回该对象的引用这两个操作,返回对象的引用可能先被执行,于是在多线程环境下,其他线程可能会看到一个 部分构造的对象
public class Main {
public static Person  person;
    public static void main(String[] args) {
        person = new Person(100);//  2
    }
}
 class Person {
     int name = 0;
     public Person(int name) {
         this.name = name;//  1
     }
 }

以上のプログラムでは、ステップ1が実行されていない場合、nameの値は依然として0であり、ステップ2が先に実行され、同時にpersonがマルチスレッド環境にパブリッシュされ、他のスレッドが見たのは完全なビューではなく、name=0を見た.このパブリケーションは安全ではないと呼ばれている.
パブリケーションは、完全に初期化されたオブジェクトであることを保証するために、安全なパブリケーションが必要です.
オブジェクトの安全な配布方法
静的初期化関数でオブジェクトを初期化する参照原理::仮想マシンは静的初期化器の初期化の対象が必ず安全に発表されることを保証する
オブジェクトのドメインをvolatileキーでマークするか、AtomicReferenceを使用してオブジェクトを保存します.
原理:volatile寸法のドメインオブジェクトは、必ず初期化が完了した後、オブジェクト参照が返されることを保証します.
オブジェクトを別のオブジェクトのfinalドメインに保存し、初期化コンストラクタでを初期化または直接ドメインで初期化する.
原理:final寸法のオブジェクトのドメインは、オブジェクト参照が戻る前に、オブジェクト内のfinalドメインが正常に初期化されたことを保証します(thisポインタがコンストラクタから逸脱しない限り).
ロックによって保存するドメインにオブジェクトを保存する.
原理:ロック保証メソッドブロック内のスレッドは、ロックが戻る前に内部のすべてのドメインが1回呼び出されることを保証するために使用されます(筆者の個人的な理解では、syncronizedキーワードを使用するだけでは、オブジェクトの完全な構造を保証するのに十分ではなく、オブジェクトの参照が事前に公開される可能性があります).
スレッドが安全なコンテナで、オブジェクトが安全に公開されることを保証する(SyncronizedMap、ConcurrencyMap、hashTableなど)次に、セキュリティパブリケーションの問題を単一のモデルと組み合わせて詳細に分析します.
静的初期化関数でオブジェクトの参照を安全に初期化
まずPersonクラスを定義します
class Person {
    private int name = 0;
    public Person(int name) {
        this.name = name;
    }
    public int getName() {
        return name;
    }
}

以下は、Personクラスを安全に初期化する最も簡単な方法です.
public class Singleton {
    private static Person person = new Person(100);
    public static Person getPerson() {
        return person;
    }
}

Personオブジェクトを初期化する作業量が大きい場合は、内部クラスを使用してPersonの初期化作業を遅らせることができます.
public class Singleton {
    private static class PersonHolder{
        public static Person person = new Person(10);
        
    }
    public static Person getPerson() {
        return PersonHolder.person;
    }
}

この方式はgetPerson()メソッドが最初に呼び出されたときにPersonHolderクラスが初期化されることを保証し,
以上の2つの方式はいずれも筆者が比較的推奨する安全初期化の方式であり、
finalまたはvolatileと組み合わせてsyncronizedキーワードを使用してオブジェクトを安全に初期化
一部の学生は、安全に見える単一のオブジェクトを次のように返す可能性があります.
class Singleton {
    private static Person person;
    public static Person getPerson() {
        if (person==null) {//   1
            synchronized (Person.class){
                if (person==null) {
                    person = new Person(100);//   2
                }
            }
        }
        return person;//   3
    }
}

                             例1
このコードは毎回同じオブジェクトを返すように見えますが、実際にはこのコードには2つの問題があります.
1.操作1と操作3は2つの一般的な読み取りであり、並べ替えられる可能性があり、操作3が先に読み取り、personがnullであることが判明した後、別のスレッドがpersonオブジェクトを初期化し、操作1がpersonが空でないことを読み取り、最後にnullを返すことは、単例モードでは許されない
二.同様に並べ替えのため、操作2がpersonの参照を発行するとき、彼のコンストラクタ
まだ構築が完了していないため、他のスレッドが初期化されたオブジェクトの一部を取得する可能性があります.これが典型的な安全でないパブリケーションです.
問題を解決するために,以下に示すように局所変数instanceを導入することができる.
class Singleton {
    private static Person person;
    public static Person getPerson() {
        Person instance = person;
        if (instance == null) {//  1
            synchronized (Person.class) {
                instance=person;
                if (instance == null) {
                    instance = new Person(100);//  2
                    person = instance;
                }
            }
        }
        return person;//  3
    }
}

ローカル変数はスレッドプライベート変数に属するため、スレッドプライベートの同じ変数の読み取り-書き込み-読み取りは並べ替えられず、操作1、操作2、操作3は並べ替えられない
これによりgetPerson()メソッドはnull値を返さないことが保証されます
次に、問題2を解決し、セキュリティパブリケーションの問題を処理します.これは本稿で重点的に注目している問題です.volatileまたはfinalを使用して、オブジェクトのセキュリティパブリケーションを保証することができます.
まずvolatileキーワードを使用します.コードは次のとおりです.
class Singleton {
    private static volatile Person person;
    public static Person getPerson() {
        Person instance = person;
        if (instance == null) {
            synchronized (Person.class) {
                instance=person;
                if (instance == null) {
                    instance = new Person(100);
                    person = instance;
                }
            }
        }
        return person;
    }
}

ここではvolatileキーワードを追加しただけで、personがオブジェクト参照を取得するたびに、そのオブジェクトが初期化されることを保証することができます.
次にfinalキーワードを使用して、安全な初期化を保証します.
class Singleton {
    private static volatile PersonWrapper personWrapper;
    public static Person getPersonWrapper() {
        PersonWrapper instance = personWrapper;
        if (instance == null) {
            synchronized (Person.class) {
                instance= personWrapper;
                if (instance == null) {
                    instance = new PersonWrapper(new Person(100));
                    personWrapper = instance;
                }
            }
        }
        return personWrapper.person;
    }
    private static class PersonWrapper {
        public final Person person;
        private PersonWrapper(Person person) {
            this.person = person;
        }
    }
}

finalも初期化のセキュリティを保証しますが、コードは直感的ではありません.
synchronizedキーのみを使用してオブジェクトの参照を安全に初期化
まず、次のコードを見てみましょう.
class Singleton {
    private static Person person;
    public static synchronized Person getPerson() {
        if (person == null) {
            person = new Person(100);//  1
        }
        return person;
    }
}
person , 1, fianl volatile , , 1 , person , , person , ,
class Singleton22 {
    private static Person person;
    public static synchronized Person getPerson() {
        if (person == null) {
            if (person == null) {
                person = new Person(100);
                person.getName();//          
            }
        }
        return person;
    }
}

に された は わりました
volatileとfinalキーワードの な
:オブジェクトが に されることを します.
:
final.finalのドメインは、 に が されず、 じ を に むことができます(ただし、コンストラクタでthisポインタが すると が する がありますので、そうしないでください)
finalタグはクラスにあり、クラスが されないことを し、すべてのメソッドがfinalタグ けされていることを します.privateメソッドのデフォルトはfinalです.
final はメソッドで き えが されないことを します
volatile
volatileはlongとdouble き み が であることを する
volatileは できる
メモリの 、volatile の き み は くのスレッドで されますが、count++という の を することはできません.count++は には3つの です. み り- - き み、 :volatile の き み は の の に するべきではありません.
:
finalキーワード オブジェクトのセキュリティパブリケーション
https://www.javamex.com/tutorials/synchronization_final.shtml
Safe Publication and Safe Initialization in Java
https://shipilev.net/blog/2014/safe-public-construction/
モード
https://blog.csdn.net/jason0539/article/details/23297037/
https://www.cnblogs.com/Ycheng/p/7169381.html
:
JAva プログラミング