(1)揮発性
揮発性の特性
volatile変数は、次の特性があります。
- 可視性。この揮発性変数(任意のスレッド)最後の書き込みを見ることができ、常に、揮発性変数の読み込み。
- そして、再配置の命令実行時間は、コンパイラを防ぐことができます。
- アトミック。ここでいうは、任意の単一の変数揮発性/書き込みのアトミック読み取りであるが、そのような複合操作と同様に、原子++揮発性ではありません。
パッケージday02。 輸入java.util.concurrent.TimeUnit。 / ** * @author :ZDC * @date:2020年3月19日 * / パブリック クラス_1VolatileTest { / * 揮発性* / ブール フラグ= 真。 公共 ボイドM(){ System.out.printlnは( "M開始" )。 一方、(フラグ){ } のSystem.out.println( "M終了" )。 } パブリック 静的 ボイドメイン(文字列[]引数){ _1VolatileTest V= 新しい_1VolatileTest()。 新しいスレッド(V ::メートル、 "T" ).start(); 試す{ TimeUnit.SECONDS.sleep( 2 )。 } キャッチ(InterruptedExceptionある電子){ e.printStackTrace(); } v.flag = 偽。 } }
可視性とは何ですか?
スレッドが共有変数を変更するとき、他のスレッドがこの変更の値を読み取ることができる可視手段。
揮発性は、軽量としてこれらの同じロックを使用している、それは共有変数「可視性」が、我々は単にマルチプロセッサの開発における単一volatile変数の読み取り/書き込みとして理解することができることを保証し、同期されています単一の読み取り/書き込み動作が同期して行われます。volatile変数修飾子適切に、そしてそれがスレッドコンテキストの切り替えやスケジューリングを起こさないためには、同期の実行のコストよりも低い場合。次の例に二つのクラスの効果を実行すると同じです。
パブリック クラスVolatileFeatureExample { 揮発性 長い V1 = 0L 。 公共 ボイドセット(長L){ V1 = L。 } 公共 ボイドgetAndIncrement(){ V1 ++ 。 } パブリック ロングのget(){ 戻りV1。 } } ---------------------------------------------- パブリック クラスVolatileFeatureExample { 長い V1 = 0L 。 国民は 、同期 のボイドセット(長L){ V1 = L。 } 公共 ボイドgetAndIncrement(){ 長い TEMP = GET()。 TEMP + = 1L 。 セット(TEMP)。 } パブリック 同期 長いGET(){ 戻りV1。 } }
それの可視性を達成するためにどのように揮発性のでしょうか?
Javaのメモリモデル:
命令がCPUで実行され、従って頻繁CPUとメモリ動作との間のメモリからデータを読み出し及び書き込みのオーバーヘッドを減らすために、より高速なCPUを実行し、キャッシュ領域が存在します
データ収集プロセス:
- データがキャッシュから取得しました
- キャッシュは、直接のリターンが存在します
- キャッシュが存在しません。
- データがメモリからデータを取得し
- データは、データキャッシュに書き込まれます
- データは、データを返さ
次のように上記のプロセスは、最初のステップは、分析され、整合性の問題につながります
メモリのデータが更新されましたが、キャッシュデータが更新されていない場合は、データキャッシュに戻って、古いデータが返されます。
ソリューション:
- バス上のプラスのLOCK#ロック
- CPUと他のコンポーネントのためだけCPUがこれを使用できるように、バスプラスロックLOCK位、(メモリなどの)他のコンポーネントへのアクセスがブロックされ、他のCPUと、通信バスによって実行されます変数のメモリ
- キャッシュ・コヒーレンシ・プロトコル
- 故障のキャッシュ内のすべてのデータをしながらデータキャッシュにアクセスする際には、メモリの変化が生じたデータの後に、優先度は、最新のデータを取得するために、障害からメモリをキャッシュに戻って、そしてリターンを書かれている場合、かどうかを判断するために失敗しました
共有変数はスレッド間のメインメモリに格納され、各スレッドは、専用のローカルメモリ、共有変数を読み書きするためのスレッドのコピーを保存するためのローカルメモリを持っています。そのため、一貫性は共有変数へのスレッドの変更後に、他のスレッドは、最新の共有変数へのアクセス権を持っていることを保証するために、すなわちどのように、上記があります。
命令の並べ替え
Javaのメモリモデル、コンパイラやプロセッサの命令が並べ替えできるようにすることが、並べ替えプロセスは、シングルスレッドプログラムの実施には影響しませんが、それはマルチスレッド実行の有効性に影響を与えます
イラスト
int i;
boolean ans;
i = 10;
ans = true;
上記のコード、i
およびans
命令シーケンス再配列するので割り当てが発生することがありans=true
、ときにi
依然としてゼロです。
volatileキーワード
使い方
- 変数の前にはプラス
volatile
のことができます
効果
- 共有変数を変更することを確認し、それが他のスレッドにすぐに表示されています
- 禁止命令再配置(すなわち、アクセスまたは変更するとき
volatile
、共有変数の時間は、上記のコードの実行を上に確保するように修正)
原理とメカニズム
- 変更
volatile
共有変数の声明、変更された値は、メモリに書き込ま必須となり、他のスレッド内のローカルメモリ障害のコピーを - volatileキーワードを追加する場合、コンパイルした後、それはロックプレフィックス命令のよりになります
- その後続の命令は、その命令の並べ替えを確保するために、ロック命令に排出されていない前と後に、ロック命令の前に排出されますないコマンド
例:: 理由怠惰な単一の設計パターンに加えて揮発性- Javaの並行処理
class SingletonClass{ 2 private static SingletonClass instance = null; 3 4 private SingletonClass() {} 5 6 public static SingletonClass getInstance() { 7 if(instance==null) { 8 synchronized ( SingletonClass.class) { 9 if(instance==null) 10 instance = new SingletonClass();//语句1 11 } 12 } 13 return instance; 14 } 15 }
上面的代码在多线程下调用可能会报错,具体报错原因:
在语句1中并不是一个原子操作,在JVM中其实是3个操作:
1.给instance分配空间、
2.调用 Singleton 的构造函数来初始化、
3.将instance对象指向分配的内存空间(instance指向分配的内存空间后就不为null了);
在JVM中的及时编译存在指令重排序的优化,也就是说不能保证1,2,3执行的顺序,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是 1-3-2,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
通过添加volatile就可以解决这种报错,因为volatile可以保证1、2、3的执行顺序,没执行玩1、2就肯定不会执行3,也就是没有执行完1、2instance一直为空
锁优化:
锁细化:不应该把锁加在整个方法上。
锁粗化:在征用特别频繁的地方。
以对象做锁时,为使它不发生改变,应该加final。
(2)CAS --无锁优化 或称自旋。
package day02; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * @author: zdc * @date: 2020-03-19 */ public class _2ActomicInteger { //int count=0; AtomicInteger count = new AtomicInteger(0); void m(){ for (int i = 0; i < 10000; i++) { count.incrementAndGet(); // count++; } } public static void main(String[] args) { _2ActomicInteger test = new _2ActomicInteger(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(test::m,"thread_"+i)); } threads.forEach((t)->t.start()); //让主线程最后运行 得到结果 threads.forEach((t)->{ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(test.count); } }
CAS算法理解 https://www.jianshu.com/p/ab2c8fce878b
对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS比较与交换的伪代码可以表示为:
do{
备份旧数据;
基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))
因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。
CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了.
package day02; import java.sql.Time; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * @author: zdc * @date: 2020-03-19 */ public class _3ABATest { private static AtomicInteger count = new AtomicInteger(10); public static void main(String[] args) {
//10-》11-》10 new Thread(()->{ System.out.println(Thread.currentThread().getName()+"预期值是10?"+count.compareAndSet(10,11)); System.out.println(Thread.currentThread().getName()+"预期值是11?"+count.compareAndSet(11,10)); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ System.out.println(Thread.currentThread().getName()+"预期值是10?"+count.compareAndSet(10,12)); },"B").start(); } }
AtomicStampedReference <整数> AtomicStampedReference = 新しい新規アンAtomicStampedReference <>(10 、1); ABA溶液は、バージョン番号に加えてもよいです