亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java線程之線程同步synchronized和volatile詳解

 更新時(shí)間:2017年11月10日 15:38:08   作者:他山之石頭  
這篇文章主要介紹了Java線程之線程同步synchronized和volatile詳解,具有一定參考價(jià)值,需要的朋友可以了解下。

上篇通過(guò)一個(gè)簡(jiǎn)單的例子說(shuō)明了線程安全與不安全,在例子中不安全的情況下輸出的結(jié)果恰好是逐個(gè)遞增的(其實(shí)是巧合,多運(yùn)行幾次,會(huì)產(chǎn)生不同的輸出結(jié)果),為什么會(huì)產(chǎn)生這樣的結(jié)果呢,因?yàn)榻⒌腃ount對(duì)象是線程共享的,一個(gè)線程改變了其成員變量num值,下一個(gè)線程正巧讀到了修改后的num,所以會(huì)遞增輸出。

要說(shuō)明線程同步問(wèn)題首先要說(shuō)明Java線程的兩個(gè)特性,可見(jiàn)性和有序性。多個(gè)線程之間是不能直接傳遞數(shù)據(jù)交互的,它們之間的交互只能通過(guò)共享變量來(lái)實(shí)現(xiàn)。拿上篇博文中的例子來(lái)說(shuō)明,在多個(gè)線程之間共享了Count類(lèi)的一個(gè)對(duì)象,這個(gè)對(duì)象是被創(chuàng)建在主內(nèi)存(堆內(nèi)存)中,每個(gè)線程都有自己的工作內(nèi)存(線程棧),工作內(nèi)存存儲(chǔ)了主內(nèi)存Count對(duì)象的一個(gè)副本,當(dāng)線程操作Count對(duì)象時(shí),首先從主內(nèi)存復(fù)制Count對(duì)象到工作內(nèi)存中,然后執(zhí)行代碼count.count(),改變了num值,最后用工作內(nèi)存Count刷新主內(nèi)存Count。當(dāng)一個(gè)對(duì)象在多個(gè)內(nèi)存中都存在副本時(shí),如果一個(gè)內(nèi)存修改了共享變量,其它線程也應(yīng)該能夠看到被修改后的值,此為可見(jiàn)性。多個(gè)線程執(zhí)行時(shí),CPU對(duì)線程的調(diào)度是隨機(jī)的,我們不知道當(dāng)前程序被執(zhí)行到哪步就切換到了下一個(gè)線程,一個(gè)最經(jīng)典的例子就是銀行匯款問(wèn)題,一個(gè)銀行賬戶(hù)存款100,這時(shí)一個(gè)人從該賬戶(hù)取10元,同時(shí)另一個(gè)人向該賬戶(hù)匯10元,那么余額應(yīng)該還是100。那么此時(shí)可能發(fā)生這種情況,A線程負(fù)責(zé)取款,B線程負(fù)責(zé)匯款,A從主內(nèi)存讀到100,B從主內(nèi)存讀到100,A執(zhí)行減10操作,并將數(shù)據(jù)刷新到主內(nèi)存,這時(shí)主內(nèi)存數(shù)據(jù)100-10=90,而B(niǎo)內(nèi)存執(zhí)行加10操作,并將數(shù)據(jù)刷新到主內(nèi)存,最后主內(nèi)存數(shù)據(jù)100+10=110,顯然這是一個(gè)嚴(yán)重的問(wèn)題,我們要保證A線程和B線程有序執(zhí)行,先取款后匯款或者先匯款后取款,此為有序性。本文講述了JDK5.0之前傳統(tǒng)線程的同步方式,更高級(jí)的同步方式可參見(jiàn)Java線程之鎖對(duì)象Lock-同步問(wèn)題更完美的處理方式代碼實(shí)例

下面同樣用代碼來(lái)展示一下線程同步問(wèn)題。

TraditionalThreadSynchronized.java:創(chuàng)建兩個(gè)線程,執(zhí)行同一個(gè)對(duì)象的輸出方法。

public class TraditionalThreadSynchronized {
	public static void main(String[] args) {
		final Outputter output = new Outputter();
		new Thread() {
			public void run() {
				output.output("zhangsan");
			}
		}.start();		
		new Thread() {
			public void run() {
				output.output("lisi");
			}
		}.start();
	}
}
class Outputter {
	public void output(String name) {
		// TODO 為了保證對(duì)name的輸出不是一個(gè)原子操作,這里逐個(gè)輸出name的每個(gè)字符
		for(int i = 0; i < name.length(); i++) {
			System.out.print(name.charAt(i));
			// Thread.sleep(10);
		}
	}
}

運(yùn)行結(jié)果:

zhlainsigsan 

顯然輸出的字符串被打亂了,我們期望的輸出結(jié)果是zhangsanlisi,這就是線程同步問(wèn)題,我們希望output方法被一個(gè)線程完整的執(zhí)行完之后再切換到下一個(gè)線程,Java中使用synchronized保證一段代碼在多線程執(zhí)行時(shí)是互斥的,有兩種用法:

1. 使用synchronized將需要互斥的代碼包含起來(lái),并上一把鎖。

{ 
 synchronized (this) { 
  for(int i = 0; i < name.length(); i++) { 
   System.out.print(name.charAt(i)); 
  } 
 } 
} 

這把鎖必須是需要互斥的多個(gè)線程間的共享對(duì)象,像下面的代碼是沒(méi)有意義的。

{ 
 Object lock = new Object(); 
 synchronized (lock) { 
  for(int i = 0; i < name.length(); i++) { 
   System.out.print(name.charAt(i)); 
  } 
 } 
} 

每次進(jìn)入output方法都會(huì)創(chuàng)建一個(gè)新的lock,這個(gè)鎖顯然每個(gè)線程都會(huì)創(chuàng)建,沒(méi)有意義。
2. 將synchronized加在需要互斥的方法上。

public synchronized void output(String name) { 
 // TODO 線程輸出方法 
 for(int i = 0; i < name.length(); i++) { 
  System.out.print(name.charAt(i)); 
 } 
} 

這種方式就相當(dāng)于用this鎖住整個(gè)方法內(nèi)的代碼塊,如果用synchronized加在靜態(tài)方法上,就相當(dāng)于用××××.class鎖住整個(gè)方法內(nèi)的代碼塊。使用synchronized在某些情況下會(huì)造成死鎖,死鎖問(wèn)題以后會(huì)說(shuō)明。使用synchronized修飾的方法或者代碼塊可以看成是一個(gè)原子操作。

每個(gè)鎖對(duì)象(JLS中叫monitor)都有兩個(gè)隊(duì)列,一個(gè)是就緒隊(duì)列,一個(gè)是阻塞隊(duì)列,就緒隊(duì)列存儲(chǔ)了將要獲得鎖的線程,阻塞隊(duì)列存儲(chǔ)了被阻塞的線程,當(dāng)一個(gè)線程被喚醒(notify)后,才會(huì)進(jìn)入到就緒隊(duì)列,等待CPU的調(diào)度,反之,當(dāng)一個(gè)線程被wait后,就會(huì)進(jìn)入阻塞隊(duì)列,等待下一次被喚醒,這個(gè)涉及到線程間的通信,下一篇博文會(huì)說(shuō)明??次覀兊睦?,當(dāng)?shù)谝粋€(gè)線程執(zhí)行輸出方法時(shí),獲得同步鎖,執(zhí)行輸出方法,恰好此時(shí)第二個(gè)線程也要執(zhí)行輸出方法,但發(fā)現(xiàn)同步鎖沒(méi)有被釋放,第二個(gè)線程就會(huì)進(jìn)入就緒隊(duì)列,等待鎖被釋放。一個(gè)線程執(zhí)行互斥代碼過(guò)程如下:

1. 獲得同步鎖;
2. 清空工作內(nèi)存;
3. 從主內(nèi)存拷貝對(duì)象副本到工作內(nèi)存;
4. 執(zhí)行代碼(計(jì)算或者輸出等);
5. 刷新主內(nèi)存數(shù)據(jù);
6. 釋放同步鎖。

所以,synchronized既保證了多線程的并發(fā)有序性,又保證了多線程的內(nèi)存可見(jiàn)性。

volatile是第二種Java多線程同步的機(jī)制,根據(jù)JLS(Java LanguageSpecifications)的說(shuō)法,一個(gè)變量可以被volatile修飾,在這種情況下內(nèi)存模型(主內(nèi)存和線程工作內(nèi)存)確保所有線程可以看到一致的變量值,來(lái)看一段代碼:

class Test { 
 static int i = 0, j = 0; 
 static void one() { 
  i++; 
  j++; 
 } 
 static void two() { 
  System.out.println("i=" + i + " j=" + j); 
 } 
} 

一些線程執(zhí)行one方法,另一些線程執(zhí)行two方法,two方法有可能打印出j比i大的值,按照之前分析的線程執(zhí)行過(guò)程分析一下:

1. 將變量i從主內(nèi)存拷貝到工作內(nèi)存;
2. 改變i的值;
3. 刷新主內(nèi)存數(shù)據(jù);
4. 將變量j從主內(nèi)存拷貝到工作內(nèi)存;
5. 改變j的值;
6. 刷新主內(nèi)存數(shù)據(jù);

這個(gè)時(shí)候執(zhí)行two方法的線程先讀取了主存i原來(lái)的值又讀取了j改變后的值,這就導(dǎo)致了程序的輸出不是我們預(yù)期的結(jié)果,要阻止這種不合理的行為的一種方式是在one方法和two方法前面加上synchronized修飾符:

class Test { 
 static int i = 0, j = 0; 
 static synchronized void one() { 
  i++; 
  j++; 
 } 
 static synchronized void two() { 
  System.out.println("i=" + i + " j=" + j); 
 } 
} 

根據(jù)前面的分析,我們可以知道,這時(shí)one方法和two方法再也不會(huì)并發(fā)的執(zhí)行了,i和j的值在主內(nèi)存中會(huì)一直保持一致,并且two方法輸出的也是一致的。另一種同步的機(jī)制是在共享變量之前加上volatile:

class Test { 
 static volatile int i = 0, j = 0; 
 static void one() { 
  i++; 
  j++; 
 } 
 static void two() { 
  System.out.println("i=" + i + " j=" + j); 
 } 
} 

one方法和two方法還會(huì)并發(fā)的去執(zhí)行,但是加上volatile可以將共享變量i和j的改變直接響應(yīng)到主內(nèi)存中,這樣保證了主內(nèi)存中i和j的值一致性,然而在執(zhí)行two方法時(shí),在two方法獲取到i的值和獲取到j(luò)的值中間的這段時(shí)間,one方法也許被執(zhí)行了好多次,導(dǎo)致j的值會(huì)大于i的值。所以volatile可以保證內(nèi)存可見(jiàn)性,不能保證并發(fā)有序性。

沒(méi)有明白JLS中為什么使用兩個(gè)變量來(lái)闡述volatile的工作原理,這樣不是很好理解。volatile是一種弱的同步手段,相對(duì)于synchronized來(lái)說(shuō),某些情況下使用,可能效率更高,因?yàn)樗皇亲枞?,尤其是讀操作時(shí),加與不加貌似沒(méi)有影響,處理寫(xiě)操作的時(shí)候,可能消耗的性能更多些。但是volatile和synchronized性能的比較,我也說(shuō)不太準(zhǔn),多線程本身就是比較玄的東西,依賴(lài)于CPU時(shí)間分片的調(diào)度,JVM更玄,還沒(méi)有研究過(guò)虛擬機(jī),從頂層往底層看往往是比較難看透的。在JDK5.0之前,如果沒(méi)有參透volatile的使用場(chǎng)景,還是不要使用了,盡量用synchronized來(lái)處理同步問(wèn)題,線程阻塞這玩意簡(jiǎn)單粗暴。另外volatile和final不能同時(shí)修飾一個(gè)字段,可以想想為什么。

總結(jié)

以上就是本文關(guān)于Java線程之線程同步synchronized和volatile詳解的全部?jī)?nèi)容,希望對(duì)大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:java多線程編程實(shí)例、創(chuàng)建并運(yùn)行一個(gè)java線程方法介紹詳解java各種集合的線程安全等,有什么問(wèn)題可以隨時(shí)留言,小編會(huì)及時(shí)回復(fù)大家的。感謝朋友們對(duì)本站的支持!

相關(guān)文章

  • java 發(fā)送http和https請(qǐng)求的實(shí)例

    java 發(fā)送http和https請(qǐng)求的實(shí)例

    下面小編就為大家分享一篇java 發(fā)送http和https請(qǐng)求的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • java如何通過(guò)IP解析地理位置

    java如何通過(guò)IP解析地理位置

    這篇文章主要介紹了java如何通過(guò)IP解析地理位置的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 深入解析Java的Spring框架中bean的依賴(lài)注入

    深入解析Java的Spring框架中bean的依賴(lài)注入

    這篇文章主要介紹了Java的Spring框架中bean的依賴(lài)注入,講解了以構(gòu)造函數(shù)為基礎(chǔ)的依賴(lài)注入和基于setter方法的依賴(lài)注入的方式,需要的朋友可以參考下
    2015-12-12
  • 淺談JAVA內(nèi)存分配與參數(shù)傳遞

    淺談JAVA內(nèi)存分配與參數(shù)傳遞

    這篇文章主要介紹了JAVA內(nèi)存分配與參數(shù)傳遞,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • springboot攔截器Interceptor的使用,你都了解嗎

    springboot攔截器Interceptor的使用,你都了解嗎

    springmvc 中的攔截器可以對(duì)請(qǐng)求進(jìn)行判別,在請(qǐng)求到達(dá)控制器之前,把非法的請(qǐng)求給攔截掉下面來(lái)說(shuō)一說(shuō), 它在springboot中的使用,感興趣的朋友一起看看吧
    2021-07-07
  • SpringBoot集成Redis實(shí)現(xiàn)驗(yàn)證碼的簡(jiǎn)單案例

    SpringBoot集成Redis實(shí)現(xiàn)驗(yàn)證碼的簡(jiǎn)單案例

    本文主要介紹了SpringBoot集成Redis實(shí)現(xiàn)驗(yàn)證碼的簡(jiǎn)單案例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java編程簡(jiǎn)單應(yīng)用

    Java編程簡(jiǎn)單應(yīng)用

    本文主要介紹了三個(gè)簡(jiǎn)單Java小程序———1、HelloWorld(HelloWorld的來(lái)源);2、輸出個(gè)人信息3、輸出特殊圖案。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • 淺析Jmeter多用戶(hù)token使用問(wèn)題

    淺析Jmeter多用戶(hù)token使用問(wèn)題

    這篇文章主要介紹了Jmeter多用戶(hù)token使用問(wèn)題,通過(guò)具體的例子給大家介紹了Jmeter多用戶(hù)token使用場(chǎng)景接口分析,需要的朋友可以參考下
    2021-10-10
  • Spring Boot 通過(guò)AOP和自定義注解實(shí)現(xiàn)權(quán)限控制的方法

    Spring Boot 通過(guò)AOP和自定義注解實(shí)現(xiàn)權(quán)限控制的方法

    這篇文章主要介紹了Spring Boot 通過(guò)AOP和自定義注解實(shí)現(xiàn)權(quán)限控制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Spring攔截器HandlerInterceptor接口代碼解析

    Spring攔截器HandlerInterceptor接口代碼解析

    這篇文章主要介紹了Spring攔截器HandlerInterceptor接口代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12

最新評(píng)論