【愚公系列】2022年01月 Java教學課程 63-原子性
一、原子性
1.volatile-問題
2.volatile解決
3.synchronized解決
4.原子性
5.volatile關鍵字不能保證原子性
6.原子性_AtomicInteger
7.AtomicInteger-內存解析
8.AtomicInteger-源碼解析
9.悲觀鎖和樂觀鎖
一、原子性
1.volatile-問題
2.volatile解決
3.synchronized解決
4.原子性
5.volatile關鍵字不能保證原子性
6.原子性_AtomicInteger
7.AtomicInteger-內存解析
8.AtomicInteger-源碼解析
9.悲觀鎖和樂觀鎖
一、原子性
1.volatile-問題
代碼分析 :
package com.itheima.myvolatile; public class Demo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); t1.setName("小路同學"); t1.start(); MyThread2 t2 = new MyThread2(); t2.setName("小皮同學"); t2.start(); } }
package com.itheima.myvolatile; public class Money { public static int money = 100000; }
package com.itheima.myvolatile; public class MyThread1 extends Thread { @Override public void run() { while(Money.money == 100000){ } System.out.println("結婚基金已經不是十萬了"); } }
package com.itheima.myvolatile; public class MyThread2 extends Thread { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } Money.money = 90000; } }
程序問題 : 女孩雖然知道結婚基金是十萬,但是當基金的余額發生變化的時候,女孩無法知道最新的余額。
2.volatile解決
以上案例出現的問題 :
當A線程修改了共享數據時,B線程沒有及時獲取到最新的值,如果還在使用原先的值,就會出現問題
1,堆內存是唯一的,每一個線程都有自己的線程棧。
2 ,每一個線程在使用堆里面變量的時候,都會先拷貝一份到變量的副本中。
3 ,在線程中,每一次使用是從變量的副本中獲取的。
Volatile關鍵字 : 強制線程每次在使用的時候,都會看一下共享區域最新的值
代碼實現 : 使用volatile關鍵字解決
package com.itheima.myvolatile; public class Demo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); t1.setName("小路同學"); t1.start(); MyThread2 t2 = new MyThread2(); t2.setName("小皮同學"); t2.start(); } }
package com.itheima.myvolatile; public class Money { public static volatile int money = 100000; }
package com.itheima.myvolatile; public class MyThread1 extends Thread { @Override public void run() { while(Money.money == 100000){ } System.out.println("結婚基金已經不是十萬了"); } }
package com.itheima.myvolatile; public class MyThread2 extends Thread { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } Money.money = 90000; } }
3.synchronized解決
synchronized解決 :
1 .線程獲得鎖
2 .清空變量副本
3 .拷貝共享變量最新的值到變量副本中
4 .執行代碼
5.將修改后變量副本中的值賦值給共享數據
6 .釋放鎖
代碼實現 :
package com.itheima.myvolatile2; public class Demo { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); t1.setName("小路同學"); t1.start(); MyThread2 t2 = new MyThread2(); t2.setName("小皮同學"); t2.start(); } }
package com.itheima.myvolatile2; public class Money { public static Object lock = new Object(); public static volatile int money = 100000; }
package com.itheima.myvolatile2; public class MyThread1 extends Thread { @Override public void run() { while(true){ synchronized (Money.lock){ if(Money.money != 100000){ System.out.println("結婚基金已經不是十萬了"); break; } } } } }
package com.itheima.myvolatile2; public class MyThread2 extends Thread { @Override public void run() { synchronized (Money.lock) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } Money.money = 90000; } } }
4.原子性
概述 : 所謂的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了執行并且不會受到任何因素的干擾而中斷,要么所有的操作都不執行,多個操作是一個不可以分割的整體。
代碼實現 :
package com.itheima.threadatom; public class AtomDemo { public static void main(String[] args) { MyAtomThread atom = new MyAtomThread(); for (int i = 0; i < 100; i++) { new Thread(atom).start(); } } } class MyAtomThread implements Runnable { private volatile int count = 0; //送冰淇淋的數量 @Override public void run() { for (int i = 0; i < 100; i++) { //1,從共享數據中讀取數據到本線程棧中. //2,修改本線程棧中變量副本的值 //3,會把本線程棧中變量副本的值賦值給共享數據. count++; System.out.println("已經送了" + count + "個冰淇淋"); } } }
代碼總結 : count++ 不是一個原子性操作, 他在執行的過程中,有可能被其他線程打斷
5.volatile關鍵字不能保證原子性
解決方案 : 我們可以給count++操作添加鎖,那么count++操作就是臨界區中的代碼,臨界區中的代碼一次只能被一個線程去執行,所以count++就變成了原子操作。
package com.itheima.threadatom2; public class AtomDemo { public static void main(String[] args) { MyAtomThread atom = new MyAtomThread(); for (int i = 0; i < 100; i++) { new Thread(atom).start(); } } } class MyAtomThread implements Runnable { private volatile int count = 0; //送冰淇淋的數量 private Object lock = new Object(); @Override public void run() { for (int i = 0; i < 100; i++) { //1,從共享數據中讀取數據到本線程棧中. //2,修改本線程棧中變量副本的值 //3,會把本線程棧中變量副本的值賦值給共享數據. synchronized (lock) { count++; System.out.println("已經送了" + count + "個冰淇淋"); } } } }
6.原子性_AtomicInteger
概述:java從JDK1.5開始提供了java.util.concurrent.atomic包(簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單,性能高效,線程安全地更新一個變量的方式。因為變
量的類型有很多種,所以在Atomic包里一共提供了13個類,屬于4種類型的原子更新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(字段)。本次我們只講解
使用原子的方式更新基本類型,使用原子的方式更新基本類型Atomic包提供了以下3個類:
AtomicBoolean: 原子更新布爾類型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新長整型
以上3個類提供的方法幾乎一模一樣,所以本節僅以AtomicInteger為例進行講解,AtomicInteger的常用方法如下:
public AtomicInteger(): 初始化一個默認值為0的原子型Integer public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer int get(): 獲取值 int getAndIncrement(): 以原子方式將當前值加1,注意,這里返回的是自增前的值。 int incrementAndGet(): 以原子方式將當前值加1,注意,這里返回的是自增后的值。 int addAndGet(int data): 以原子方式將輸入的數值與實例中的值(AtomicInteger里的value)相加,并返回結果。 int getAndSet(int value): 以原子方式設置為newValue的值,并返回舊值。
代碼實現 :
package com.itheima.threadatom3; import java.util.concurrent.atomic.AtomicInteger; public class MyAtomIntergerDemo1 { // public AtomicInteger(): 初始化一個默認值為0的原子型Integer // public AtomicInteger(int initialValue): 初始化一個指定值的原子型Integer public static void main(String[] args) { AtomicInteger ac = new AtomicInteger(); System.out.println(ac); AtomicInteger ac2 = new AtomicInteger(10); System.out.println(ac2); } }
package com.itheima.threadatom3; import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicInteger; public class MyAtomIntergerDemo2 { // int get(): 獲取值 // int getAndIncrement(): 以原子方式將當前值加1,注意,這里返回的是自增前的值。 // int incrementAndGet(): 以原子方式將當前值加1,注意,這里返回的是自增后的值。 // int addAndGet(int data): 以原子方式將參數與對象中的值相加,并返回結果。 // int getAndSet(int value): 以原子方式設置為newValue的值,并返回舊值。 public static void main(String[] args) { // AtomicInteger ac1 = new AtomicInteger(10); // System.out.println(ac1.get()); // AtomicInteger ac2 = new AtomicInteger(10); // int andIncrement = ac2.getAndIncrement(); // System.out.println(andIncrement); // System.out.println(ac2.get()); // AtomicInteger ac3 = new AtomicInteger(10); // int i = ac3.incrementAndGet(); // System.out.println(i);//自增后的值 // System.out.println(ac3.get()); // AtomicInteger ac4 = new AtomicInteger(10); // int i = ac4.addAndGet(20); // System.out.println(i); // System.out.println(ac4.get()); AtomicInteger ac5 = new AtomicInteger(100); int andSet = ac5.getAndSet(20); System.out.println(andSet); System.out.println(ac5.get()); } }
7.AtomicInteger-內存解析
AtomicInteger原理 : 自旋鎖 + CAS 算法
CAS算法:
有3個操作數(內存值V, 舊的預期值A,要修改的值B)
當舊的預期值A == 內存值 此時修改成功,將V改為B
當舊的預期值A!=內存值 此時修改失敗,不做任何操作
并重新獲取現在的最新值(這個重新獲取的動作就是自旋)
8.AtomicInteger-源碼解析
代碼實現 :
package com.itheima.threadatom4; public class AtomDemo { public static void main(String[] args) { MyAtomThread atom = new MyAtomThread(); for (int i = 0; i < 100; i++) { new Thread(atom).start(); } } }
package com.itheima.threadatom4; import java.util.concurrent.atomic.AtomicInteger; public class MyAtomThread implements Runnable { //private volatile int count = 0; //送冰淇淋的數量 //private Object lock = new Object(); AtomicInteger ac = new AtomicInteger(0); @Override public void run() { for (int i = 0; i < 100; i++) { //1,從共享數據中讀取數據到本線程棧中. //2,修改本線程棧中變量副本的值 //3,會把本線程棧中變量副本的值賦值給共享數據. //synchronized (lock) { // count++; // ac++; int count = ac.incrementAndGet(); System.out.println("已經送了" + count + "個冰淇淋"); // } } } }
源碼解析 :
//先自增,然后獲取自增后的結果 public final int incrementAndGet() { //+ 1 自增后的結果 //this 就表示當前的atomicInteger(值) //1 自增一次 return U.getAndAddInt(this, VALUE, 1) + 1; } public final int getAndAddInt(Object o, long offset, int delta) { //v 舊值 int v; //自旋的過程 do { //不斷的獲取舊值 v = getIntVolatile(o, offset); //如果這個方法的返回值為false,那么繼續自旋 //如果這個方法的返回值為true,那么自旋結束 //o 表示的就是內存值 //v 舊值 //v + delta 修改后的值 } while (!weakCompareAndSetInt(o, offset, v, v + delta)); //作用:比較內存中的值,舊值是否相等,如果相等就把修改后的值寫到內存中,返回true。表示修改成功。 // 如果不相等,無法把修改后的值寫到內存中,返回false。表示修改失敗。 //如果修改失敗,那么繼續自旋。 return v; }
9.悲觀鎖和樂觀鎖
synchronized和CAS的區別 :
**相同點:**在多線程情況下,都可以保證共享數據的安全性。
**不同點:**synchronized總是從最壞的角度出發,認為每次獲取數據的時候,別人都有可能修改。所以在每 次操作共享數據之前,都會上鎖。(悲觀鎖)
cas是從樂觀的角度出發,假設每次獲取數據別人都不會修改,所以不會上鎖。只不過在修改共享數據的時候,會檢查一下,別人有沒有修改過這個數據。
如果別人修改過,那么我再次獲取現在最新的值。
如果別人沒有修改過,那么我現在直接修改共享數據的值.(樂觀鎖)
5G教育 Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。