并发编程三大特性
并發編程三大特性:原子性,可見性,有序性
1.什么是原子性?
原子性是指在一個操作中,所有的子操作都是一個整體,要么同時全部執行,要么同時不執行,且在執行過程中,不能被掛起,直到執行完。可能這樣解釋很懵,那么看下面例子就全明白了。
int等不大于32位的基本類型的操作都是具有原子性,對于long和double變量,把它們作為2個原子性的32位值來對待,而不是一個原子性的64位值, 這樣將一個long型的值保存到內存的時候,可能是2次32位的寫操作, 2個競爭線程想寫不同的值到內存的時候,可能導致內存中的值是不正確的結果。
nt i=1,這是原子操作;
double i=1 這不是一個,因為double是64位數據類型。
x=y也不是原子操作,因為執行這個操作需要三步,要先從內存中拿取y的值,然后對x值進行修改,然后把x的值寫回內存中。
2.可見性
可見性就是指當一個線程修改了線程共享變量的值,其它線程能夠立即得知這個修改。
講可見性,就必須先了解JMM模型(java內存模型),在JMM中,主存中有A=2,子線程復制一份副本放在自己的工作內存中,如果要對A進行操作修改,那么要先修改自己工作內存的A,然后再將數據同步到主存中。
3.有序性?
指令重排是指在程序執行過程中,為了提高性能, 編譯器和CPU可能會對指令進行重新排序,在單線程中這完全沒問題,還能提高性能,而在多線程中卻有很多問題,會出現重排后結果不一致問題。為了解決這一問題就會防止指令重排,采用內存屏障來確保指令不會被重排序,下面是內存屏障的原理,了解即可。?
?那么java是通過什么來預防三大特性所帶來的問題的呢?
原子性
當我們執行以下操作時,創建10個線程,每個線程循環10000此次,每次循環對value+1,按照如此邏輯,打印結果應該時100000,而運行以下代碼,每次結果都會不一樣,因為他不能保證原子性,當線程1對value進行加1操作,還沒回寫主存中,時間片就輪轉到線程2對value進行加1操作,但是線程2對數據進行了回寫主存,那么此時主存的value的值為1,再次時間片輪轉到線程1,而線程1就差數據回寫沒完成,那么執行回寫操作,將value的值又改為了1,此時線程1修改的值被更新丟失(類似數據庫的丟失更新)。
public class DefaultTest {private static int value = 0;public static void main(String[] args) throws InterruptedException {//創建10個線程for (int i = 0; i < 10; i++) {new Thread(() -> {//每個線程對value加10000for (int j = 0; j < 10000; j++) {value++;}}).start();}Thread.sleep(2000);;System.out.println(value);} }java為了解決上面并發帶來的問題引入了java.util.concurrent.atomic包,此包下的所有類都是線程安全的,保證了原子性。以下用AtomicInteger來替代int
public class AtomicIntegerTest {private static AtomicInteger value = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 10000; j++) {value.addAndGet(1);}}).start();}Thread.sleep(2000);System.out.println(value);} }也可以用synchronized來保證原子性,可見性,有序性,這是重量級鎖。對于不同情況采用不同應對方式,這里就不講synchronized了。
可見性
當我們執行以下代碼,創建兩個線程,線程t1將線程t2的stop改為true,按照邏輯,此時線程t2應該會退出循環,然而并沒有這樣子。因為線程t1對線程t2的值進行修改后,線程t2時沒有感知到,這就是數據不可見問題,那么java對這種問題,引入了volatile關鍵字。
public class u_volatile {public static void main(String[] args) throws InterruptedException {Task task = new Task();Thread t1 = new Thread(task, "線程t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);System.out.println("開始通知線程停止");task.stop = true;} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();Thread.sleep(1000);} }class Task implements Runnable {boolean stop = false;//屬性int i = 0;@Overridepublic void run() {long s = System.currentTimeMillis();while (!stop) {i++;}System.out.println("線程退出" + (System.currentTimeMillis() - s));} }volatile關鍵字只能修改全局變量,對屬性stop加入volatile修飾,就能保住可見性了
public class u_volatile {public static void main(String[] args) throws InterruptedException {Task task = new Task();Thread t1 = new Thread(task, "線程t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);System.out.println("開始通知線程停止");task.stop = true;} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();Thread.sleep(1000);} }class Task implements Runnable {volatile boolean stop = false;//屬性int i = 0;@Overridepublic void run() {long s = System.currentTimeMillis();while (!stop) {i++;}System.out.println("線程退出" + (System.currentTimeMillis() - s));} }總結?
在java中通過atomic來保證原子性;volatile保證可見性、禁止指令重排,但是不保證原子性;synchronized保證了有序,可見,原子性,volatile不會造成線程阻塞,synchronized可能會造成線程的阻塞,所以后面才有鎖優化和無鎖編程。
總結
- 上一篇: hdu 3001 tsp问题/三进制
- 下一篇: 计算机网络组成原理概述概述