摘要:關(guān)鍵字總結(jié)有個(gè)關(guān)鍵字,它們是接下來對(duì)其中常用的幾個(gè)關(guān)鍵字進(jìn)行概括。而通過關(guān)鍵字,并不能解決非原子操作的線程安全性。為了在一個(gè)特定對(duì)象的一個(gè)域上關(guān)閉,可以在這個(gè)域前加上關(guān)鍵字。是語言的關(guān)鍵字,用來表示一個(gè)域不是該對(duì)象串行化的一部分。
java 關(guān)鍵字總結(jié)
Java有50個(gè)關(guān)鍵字,它們是:
abstract do implements private throw Boolean double import protected throws break else instanceof public transient byte extends int return true case false interface short try catch final long static void char finally native super volatile class float new switch while continue for null synchronized const default if package this goto
接下來對(duì)其中常用的幾個(gè)關(guān)鍵字進(jìn)行概括。
public private protected
public,protected,private是Java里用來定義成員的訪問權(quán)限的,另外還有一種是“default”,也就是在成員前不加任何權(quán)限修飾符。
這四個(gè)修飾詞de訪問權(quán)限見下:
-- | 類內(nèi)部 | package內(nèi) | 子類 | 其他 |
---|---|---|---|---|
public | 允許 | 允許 | 允許 | 允許 |
protected | 允許 | 允許 | 允許 | 不允許 |
default | 允許 | 允許 | 不允許 | 不允許 |
private | 允許 | 不允許 | 不允許 | 不允許 |
比如:用protected修飾的成員(變量或方法),在類內(nèi)部可以調(diào)用,同一個(gè)package下的其他類也可以調(diào)用,子類里也可以調(diào)用,其他地方則不可以調(diào)用,也就是說在其他。
在java中,除了這四種修飾詞外,還有其他如abstract、static、final等11個(gè)修飾詞。
public
使用對(duì)象:類、接口、成員
介紹:無論它所處在的包定義在哪,該類(接口、成員)都是可訪問的
private
使用對(duì)象:成員
介紹:成員只可以在定義它的類中被訪問
static
使用對(duì)象:類、方法、字段、初始化函數(shù)
介紹:成名為static的內(nèi)部類是一個(gè)頂級(jí)類,它和包含類的成員是不相關(guān)的。靜態(tài)方法是類方法,是被指向到所屬的類而不是類的實(shí)例。靜態(tài)字段是類字段,無論該字段所在的類創(chuàng)建了多少實(shí)例,該字段只存在一個(gè)實(shí)例被指向到所屬的類而不是類的實(shí)例。初始化函數(shù)是在裝載類時(shí)執(zhí)行的,而不是在創(chuàng)建實(shí)例時(shí)執(zhí)行的。
final
使用對(duì)象:類、方法、字段、變量
介紹:被定義成final的類不允許出現(xiàn)子類,不能被覆蓋(不應(yīng)用于動(dòng)態(tài)查詢),字段值不允許被修改。
abstract
使用對(duì)象:類、接口、方法
介紹:類中包括沒有實(shí)現(xiàn)的方法,不能被實(shí)例化。如果是一個(gè)abstract方法,則方法體為空,該方法的實(shí)現(xiàn)在子類中被定義,并且包含一個(gè)abstract方法的類必須是一個(gè)abstract類
protected
使用對(duì)象:成員
介紹:成員只能在定義它的包中被訪問,如果在其他包中被訪問,則實(shí)現(xiàn)這個(gè)方法的類必須是該成員所屬類的子類。
native
使用對(duì)象:成員
介紹:與操作平臺(tái)相關(guān),定義時(shí)并不定義其方法,方法的實(shí)現(xiàn)被一個(gè)外部的庫實(shí)現(xiàn)。native可以與所有其它的java標(biāo)識(shí)符連用,但是abstract除外。
public native int hashCode();
strictfp
使用對(duì)象:類、方法
介紹:strictfp修飾的類中所有的方法都隱藏了strictfp修飾詞,方法執(zhí)行的所有浮點(diǎn)計(jì)算遵守IEEE 754標(biāo)準(zhǔn),所有取值包括中間的結(jié)果都必須表示為float或double類型,而不能利用由本地平臺(tái)浮點(diǎn)格式或硬件提供的額外精度或表示范圍。
synchronized
使用對(duì)象:方法
介紹:對(duì)于一個(gè)靜態(tài)的方法,在執(zhí)行之前jvm把它所在的類鎖定;對(duì)于一個(gè)非靜態(tài)類的方法,執(zhí)行前把某個(gè)特定對(duì)象實(shí)例鎖定。
volatile
使用對(duì)象:字段
介紹:因?yàn)楫惒骄€程可以訪問字段,所以有些優(yōu)化操作是一定不能作用在字段上的。volatile有時(shí)可以代替synchronized。
transient
使用對(duì)象:字段
介紹:字段不是對(duì)象持久狀態(tài)的一部分,不應(yīng)該把字段和對(duì)象一起串起。
volatile
先補(bǔ)充一下概念:Java 內(nèi)存模型中的可見性、原子性和有序性。 可見性: 可見性是一種復(fù)雜的屬性,因?yàn)榭梢娦灾械腻e(cuò)誤總是會(huì)違背我們的直覺。通常,我們無法確保執(zhí)行讀操作的線程能適時(shí)地看到其他線程寫入的值,有時(shí)甚至是根本不可能的事情。為了確保多個(gè)線程之間對(duì)內(nèi)存寫入操作的可見性,必須使用同步機(jī)制。 可見性,是指線程之間的可見性,一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見的。也就是一個(gè)線程修改的結(jié)果。另一個(gè)線程馬上就能看到。比如:用volatile修飾的變量,就會(huì)具有可見性。volatile修飾的變量不允許線程內(nèi)部緩存和重排序,即直接修改內(nèi)存。所以對(duì)其他線程是可見的。但是這里需要注意一個(gè)問題,volatile只能讓被他修飾內(nèi)容具有可見性,但不能保證它具有原子性。比如 volatile int a = 0;之后有一個(gè)操作 a++;這個(gè)變量a具有可見性,但是a++ 依然是一個(gè)非原子操作,也就是這個(gè)操作同樣存在線程安全問題。 在 Java 中 volatile、synchronized 和 final 實(shí)現(xiàn)可見性。 原子性: 原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型) 這個(gè)操作是不可分割的,那么我們說這個(gè)操作時(shí)原子操作。再比如:a++; 這個(gè)操作實(shí)際是a = a + 1;是可分割的,所以他不是一個(gè)原子操作。非原子操作都會(huì)存在線程安全問題,需要我們使用同步技術(shù)(sychronized)來讓它變成一個(gè)原子操作。一個(gè)操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。 在 Java 中 synchronized 和在 lock、unlock 中操作保證原子性。 有序性: Java 語言提供了 volatile 和 synchronized 兩個(gè)關(guān)鍵字來保證線程之間操作的有序性,volatile 是因?yàn)槠浔旧戆敖怪噶钪嘏判颉钡恼Z義,synchronized 是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作”這條規(guī)則獲得的,此規(guī)則決定了持有同一個(gè)對(duì)象鎖的兩個(gè)同步塊只能串行執(zhí)行。
用volatile修飾的變量,線程在每次使用變量的時(shí)候,都會(huì)讀取變量修改后的最的值。volatile很容易被誤用,用來進(jìn)行原子性操作。
volatile強(qiáng)制要求了所有線程在使用變量的時(shí)候要去公共內(nèi)存堆中獲取值, 不可以偷懶使用自己的.
volatile絕對(duì)不保證原子性, 原子性只能用Synchronized同步修飾符實(shí)現(xiàn).
下面看一個(gè)例子:
public class Counter { public static int count = 0; public static void inc() { //這里延遲1毫秒,使得結(jié)果明顯 try { Thread.sleep(1); } catch (InterruptedException e) { } count++; } public static void main(String[] args) { //同時(shí)啟動(dòng)1000個(gè)線程,去進(jìn)行i++計(jì)算,看看實(shí)際結(jié)果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); } }).start(); } //這里每次運(yùn)行的值都有可能不同,可能為1000 System.out.println("運(yùn)行結(jié)果:Counter.count=" + Counter.count); } }
運(yùn)行結(jié)果:Counter.count=995
實(shí)際運(yùn)算結(jié)果每次可能都不一樣,本機(jī)的結(jié)果為:運(yùn)行結(jié)果:Counter.count=995,可以看出,在多線程的環(huán)境下,Counter.count并沒有期望結(jié)果是1000。
很多人以為,這個(gè)是多線程并發(fā)問題,只需要在變量count之前加上volatile就可以避免這個(gè)問題,那我們?cè)谛薷拇a看看,看看結(jié)果是不是符合我們的期望
public class Counter { public volatile static int count = 0; public static void inc() { //這里延遲1毫秒,使得結(jié)果明顯 try { Thread.sleep(1); } catch (InterruptedException e) { } count++; } public static void main(String[] args) { //同時(shí)啟動(dòng)1000個(gè)線程,去進(jìn)行i++計(jì)算,看看實(shí)際結(jié)果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); } }).start(); } //這里每次運(yùn)行的值都有可能不同,可能為1000 System.out.println("運(yùn)行結(jié)果:Counter.count=" + Counter.count); } }
運(yùn)行結(jié)果:Counter.count=992
運(yùn)行結(jié)果還是沒有我們期望的1000,下面我們分析一下原因:
在 java 垃圾回收整理一文中,描述了jvm運(yùn)行時(shí)刻內(nèi)存的分配。其中有一個(gè)內(nèi)存區(qū)域是jvm虛擬機(jī)棧,每一個(gè)線程運(yùn)行時(shí)都有一個(gè)線程棧,線程棧保存了線程運(yùn)行時(shí)候變量值信息。當(dāng)線程訪問某一個(gè)對(duì)象時(shí)候值的時(shí)候,首先通過對(duì)象的引用找到對(duì)應(yīng)在堆內(nèi)存的變量的值,然后把堆內(nèi)存變量的具體值load到線程本地內(nèi)存中,建立一個(gè)變量副本,之后線程就不再和對(duì)象在堆內(nèi)存變量值有任何關(guān)系,而是直接修改副本變量的值,在修改完之后的某一個(gè)時(shí)刻(線程退出之前),自動(dòng)把線程變量副本的值回寫到對(duì)象在堆中變量。這樣在堆中的對(duì)象的值就產(chǎn)生變化了。
read and load 從主存復(fù)制變量到當(dāng)前工作內(nèi)存
use and assign 執(zhí)行代碼,改變共享變量值
store and write 用工作內(nèi)存數(shù)據(jù)刷新主存相關(guān)內(nèi)容
其中use and assign 可以多次出現(xiàn)
但是這一些操作并不是原子性,也就是在read load之后,如果主內(nèi)存count變量發(fā)生修改之后,線程工作內(nèi)存中的值由于已經(jīng)加載,不會(huì)產(chǎn)生對(duì)應(yīng)的變化,所以計(jì)算出來的結(jié)果會(huì)和預(yù)期不一樣。
對(duì)于volatile修飾的變量,jvm虛擬機(jī)只是保證從主內(nèi)存加載到線程工作內(nèi)存的值是最新的
例如假如線程1,線程2 在進(jìn)行read,load操作中,發(fā)現(xiàn)主內(nèi)存中count的值都是5,那么都會(huì)加載這個(gè)最新的值。在線程1堆count進(jìn)行修改之后,會(huì)write到主內(nèi)存中,主內(nèi)存中的count變量就會(huì)變?yōu)?,線程2由于已經(jīng)進(jìn)行read,load操作,在進(jìn)行運(yùn)算之后,也會(huì)更新主內(nèi)存count的變量值為6,導(dǎo)致兩個(gè)線程及時(shí)用volatile關(guān)鍵字修改之后,還是會(huì)存在并發(fā)的情況。
測(cè)試volatile、AtomicInteger這是美團(tuán)一面面試官的一個(gè)問題:i++;在多線程環(huán)境下是否存在問題?如果存在,那怎么解決?。。。大部分人會(huì)說加鎖或者synchronized同步方法。那有沒有更好的方法?
示例代碼:
public class IncrementTestDemo { public static int count = 0; public static Counter counter = new Counter(); public static AtomicInteger atomicInteger = new AtomicInteger(0); volatile public static int countVolatile = 0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread() { public void run() { for (int j = 0; j < 1000; j++) { count++; counter.increment(); atomicInteger.getAndIncrement(); countVolatile++; } } }.start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("static count: " + count); System.out.println("Counter: " + counter.getValue()); System.out.println("AtomicInteger: " + atomicInteger.intValue()); System.out.println("countVolatile: " + countVolatile); } } class Counter { private int value; public synchronized int getValue() { return value; } public synchronized int increment() { return ++value; } public synchronized int decrement() { return --value; } } 輸出結(jié)果為: static count: 9952 Counter: 10000 AtomicInteger: 10000 countVolatile: 9979
通過上面的例子說明,要解決自增操作在多線程環(huán)境下線程不安全的問題,可以選擇使用Java提供的原子類,或者使用synchronized同步方法。
而通過Volatile關(guān)鍵字,并不能解決非原子操作的線程安全性。
結(jié)論分析:雖然遞增操作++i是一種緊湊的語法,使其看上去只是一個(gè)操作,但這個(gè)操作并非原子的,因而它并不會(huì)作為一個(gè)不可分割的操作來執(zhí)行。實(shí)際上,它包含了三個(gè)獨(dú)立的操作:讀取count的值,將值加1,然后將計(jì)算結(jié)果寫入count。這是一個(gè)“讀取 - 修改 - 寫入”的操作序列,并且其結(jié)果狀態(tài)依賴于之前的狀態(tài)。
使用建議:在兩個(gè)或者更多的線程訪問的成員變量上使用volatile。當(dāng)要訪問的變量已在synchronized代碼塊中,或者為常量時(shí),不必使用。
由于使用volatile屏蔽掉了VM中必要的代碼優(yōu)化,所以在效率上比較低,因此一定在必要時(shí)才使用此關(guān)鍵字。
參考文獻(xiàn):Java中Volatile關(guān)鍵字詳解
參考文獻(xiàn):Java自增原子性問題(測(cè)試Volatile、AtomicInteger)
transient
Java的serialization提供了一種持久化對(duì)象實(shí)例的機(jī)制。當(dāng)持久化對(duì)象時(shí),可能有一個(gè)特殊的對(duì)象數(shù)據(jù)成員,我們不想 用serialization機(jī)制來保存它。為了在一個(gè)特定對(duì)象的一個(gè)域上關(guān)閉serialization,可以在這個(gè)域前加上關(guān)鍵字transient。 transient是Java語言的關(guān)鍵字,用來表示一個(gè)域不是該對(duì)象串行化的一部分。當(dāng)一個(gè)對(duì)象被串行化的時(shí)候,transient型變量的值不包括在串行化的表示中,然而非transient型的變量是被包括進(jìn)去的。 注意static變量也是可以串行化的
下面使用實(shí)例可以看出效果:
public class Login implements java.io.Serializable { private Date now = new Date(); private String uid; private transient String pwd; LoggingInfo(String username, String password) { uid = username; pwd = password; } public String toString() { String password=null; if(pwd == null) { password = "NOT SET"; } else { password = pwd; } return "logon info: " + "username: " + uid + " login date : " + now.toString() + " password: " + password; } }
現(xiàn)在我們創(chuàng)建一個(gè)這個(gè)類的實(shí)例,并且串行化(serialize)它 ,然后將這個(gè)串行化對(duì)象寫如磁盤。
Login login = new Login("yy", "123456"); System.out.println(login.toString()); try { ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("login.out")); o.writeObject(login); o.close(); } catch(Exception e) {//deal with exception} To read the object back, we can write try { ObjectInputStream in =new ObjectInputStream( new FileInputStream("logInfo.out")); LoggingInfo logInfo = (LoggingInfo)in.readObject(); System.out.println(logInfo.toString()); } catch(Exception e) {//deal with exception}
如果我們運(yùn)行這段代碼,我們會(huì)注意到從磁盤中讀回(read——back (de-serializing))的對(duì)象打印password為"NOT SET"。這是當(dāng)我們定義pwd域?yàn)閠ransient時(shí),所期望的正確結(jié)果。
現(xiàn)在,讓我們來看一下粗心對(duì)待transient域可能引起的潛在問題。假設(shè)我們修改了類定義,提供給transient域一個(gè)默認(rèn)值,
代碼如下:
public class Login implements java.io.Serializable { private Date now = new Date(); private String uid; private transient String pwd; Login() { uid = "guest"; pwd = "guest"; } public String toString() { //same as above } }
現(xiàn)在,如果我們串行化Login的一個(gè)實(shí)例,將它寫入磁盤,并且再將它從磁盤中讀出,我們?nèi)匀豢吹阶x回的對(duì)象打印password 為 "NOT SET"。當(dāng)從磁盤中讀出某個(gè)類的實(shí)例時(shí),實(shí)際上并不會(huì)執(zhí)行這個(gè)類的構(gòu)造函數(shù), 而是載入了一個(gè)該類對(duì)象的持久化狀態(tài),并將這個(gè)狀態(tài)賦值給該類的另一個(gè)對(duì)象。
參考文獻(xiàn):Java transient關(guān)鍵字
static
Java語言的關(guān)鍵字,用來定義一個(gè)變量為類變量。類只維護(hù)一個(gè)類變量的拷貝,不管該類當(dāng)前有多少個(gè)實(shí)例。"static" 同樣能夠用來定義一個(gè)方法為類方法。類方法通過類名調(diào)用而不是特定的實(shí)例,并且只能操作類變量。
static這塊面試經(jīng)常問的則是:Java的初始化塊、靜態(tài)初始化塊、構(gòu)造函數(shù)的執(zhí)行順序及用途
執(zhí)行順序:
寫一個(gè)簡(jiǎn)單的demo來實(shí)驗(yàn):
class A { static { System.out.println("Static init A."); } { System.out.println("Instance init A."); } A() { System.out.println("Constructor A."); } } class B extends A { static { System.out.println("Static init B."); } { System.out.println("Instance init B."); } B() { System.out.println("Constructor B."); } } class C extends B { static { System.out.println("Static init C."); } { System.out.println("Instance init C."); } C() { System.out.println("Constructor C."); } } public class Main { static { System.out.println("Static init Main."); } { System.out.println("Instance init Main."); } public Main() { System.out.println("Constructor Main."); } public static void main(String[] args) { C c = new C(); //B b = new B(); }
當(dāng)然這里不使用內(nèi)部類,因?yàn)?=內(nèi)部類不能使用靜態(tài)的定義==;而用靜態(tài)內(nèi)部類就失去了一般性。
執(zhí)行main方法,結(jié)果為:
Static init Main. Static init A. Static init B. Static init C. Instance init A. Constructor A. Instance init B. Constructor B. Instance init C. Constructor C.
由以上結(jié)果我們可以發(fā)現(xiàn):
先執(zhí)行了Main類的靜態(tài)塊,接下來分別是A、B、C類的靜態(tài)塊,然后是A、B、C的初始化塊和構(gòu)造函數(shù)。其中,Main類的構(gòu)造函數(shù)沒有執(zhí)行。
所有的靜態(tài)初始化塊都優(yōu)先執(zhí)行,其次才是非靜態(tài)的初始化塊和構(gòu)造函數(shù),它們的執(zhí)行順序是:
父類的靜態(tài)初始化塊
子類的靜態(tài)初始化塊
父類的初始化塊
父類的構(gòu)造函數(shù)
子類的初始化塊
子類的構(gòu)造函數(shù)
總結(jié):
靜態(tài)初始化塊的優(yōu)先級(jí)最高,也就是最先執(zhí)行,并且僅在類第一次被加載時(shí)執(zhí)行;
非靜態(tài)初始化塊和構(gòu)造函數(shù)后執(zhí)行,并且在每次生成對(duì)象時(shí)執(zhí)行一次;
非靜態(tài)初始化塊的代碼會(huì)在類構(gòu)造函數(shù)之前執(zhí)行。因此若要使用,應(yīng)當(dāng)養(yǎng)成把初始化塊寫在構(gòu)造 函數(shù)之前的習(xí)慣,便于調(diào)試;
靜態(tài)初始化塊既可以用于初始化靜態(tài)成員變量,也可以執(zhí)行初始化代碼;
非靜態(tài)初始化塊可以針對(duì)多個(gè)重載構(gòu)造函數(shù)進(jìn)行代碼復(fù)用。
拓展:
在spring中,如果在某一個(gè)類中使用初始化塊或者靜態(tài)塊的話,要注意一點(diǎn):不能再靜態(tài)塊或者初始化塊中使用其他注入容器的bean或者帶有@Value注解的變量值,因?yàn)樵撿o態(tài)塊或者初始化塊會(huì)在spring容器初始化bean之前就執(zhí)行,這樣的話,在塊中拿到的值則為null。但是如果只是要執(zhí)行一下其他的操作(沒有引用其他注入容器的bean或者帶有@Value注解的變量值)時(shí),則可以代替@PostConstruct或者implement InitializingBean 類。
synchronized
synchronized,Java同步關(guān)鍵字。用來標(biāo)記方法或者代碼塊是同步的。Java同步塊用來避免線程競(jìng)爭(zhēng)。同步塊在Java中是同步在某個(gè)對(duì)象上。所有同步在一個(gè)對(duì)象上的同步塊在同時(shí)只能被一個(gè)線程進(jìn)入并執(zhí)行操作。所有其他等待進(jìn)入該同步塊的線程將被阻塞,直到執(zhí)行該同步塊中的線程退出。
有四種不同的同步塊:
實(shí)例方法
靜態(tài)方法
實(shí)例方法中的同步塊
靜態(tài)方法中的同步塊
上述同步塊都同步在不同對(duì)象上。實(shí)際需要那種同步塊視具體情況而定。
1.實(shí)例方法同步
下面是一個(gè)同步的實(shí)例方法:
public synchronized void add(int value){ this.count += value; }
在方法聲明synchronized關(guān)鍵字,告訴Java該方法是同步的。
Java實(shí)例方法同步是同步在擁有該方法的對(duì)象上。只有一個(gè)線程能夠在實(shí)例方法同步塊中運(yùn)行。如果有多個(gè)實(shí)例存在,那么一個(gè)線程一次可以在一個(gè)實(shí)例同步塊中執(zhí)行操作。一個(gè)實(shí)例一個(gè)線程。
2.靜態(tài)方法同步
Java靜態(tài)方法同步如下示例:
public static synchronized void add(int value){ this.count += value; }
同樣,這里synchronized 關(guān)鍵字告訴Java這個(gè)方法是同步的。
靜態(tài)方法的同步是指同步在該方法所在的類對(duì)象上。因?yàn)樵贘ava虛擬機(jī)中一個(gè)類只能對(duì)應(yīng)一個(gè)類對(duì)象,而靜態(tài)方法是類對(duì)象所持有的,所以同時(shí)只允許一個(gè)線程執(zhí)行同一個(gè)類中的靜態(tài)同步方法。
3.實(shí)例方法中的同步塊
在非同步的Java方法中的同步塊的例子如下所示:
public void add(int value){ synchronized(this){ this.count += value; } }
注意Java同步塊構(gòu)造器用括號(hào)將對(duì)象括起來。在上例中,使用了“this”,即為調(diào)用add方法的實(shí)例本身。在同步構(gòu)造器中用括號(hào)括起來的對(duì)象叫做監(jiān)視器對(duì)象。上述代碼使用監(jiān)視器對(duì)象同步,同步實(shí)例方法使用調(diào)用方法本身的實(shí)例作為監(jiān)視器對(duì)象。
一次只有一個(gè)線程能夠在同步于同一個(gè)監(jiān)視器對(duì)象的Java方法內(nèi)執(zhí)行。
下面兩個(gè)例子都同步他們所調(diào)用的實(shí)例對(duì)象上,因此他們?cè)谕降膱?zhí)行效果上是等效的。
public class MyClass { public synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public void log2(String msg1, String msg2){ synchronized(this){ log.writeln(msg1); log.writeln(msg2); } } }
在上例中,每次只有一個(gè)線程能夠在兩個(gè)同步塊中任意一個(gè)方法內(nèi)執(zhí)行。
如果第二個(gè)同步塊不是同步在this實(shí)例對(duì)象上,那么兩個(gè)方法可以被線程同時(shí)執(zhí)行。
4.靜態(tài)方法中的同步塊
public class MyClass { public static synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public static void log2(String msg1, String msg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } }
這兩個(gè)方法不允許同時(shí)被線程訪問。
如果第二個(gè)同步塊不是同步在MyClass.class這個(gè)對(duì)象上。那么這兩個(gè)方法可以同時(shí)被線程訪問。
關(guān)于synchronized的用法,很多人用它的時(shí)候都會(huì)理解偏差。我們來看一個(gè)例子,下面這個(gè)例子也是很經(jīng)典的。
public class Demo { public void synchronize A() { //do something... } public void synchronized B() { //do something... } public void C() { synchronized(this){ //do something } } }
很多人認(rèn)為在多線程情況下,線程執(zhí)行A方法和B方法時(shí)可以同時(shí)進(jìn)行的,其實(shí)是錯(cuò)的。如果是不同的實(shí)例,當(dāng)然不會(huì)有影響,但是那樣synchronized就會(huì)失去意義。還有一種情況下,在使用spring進(jìn)行web開發(fā)時(shí),ApplicationContext容器默認(rèn)所有的bean都是單例的,所以在這種情況下,同一時(shí)間,只能有一個(gè)線程進(jìn)入A方法或者B方法。這樣的話,在A方法和B方法上分別加synchronized就失去了高并發(fā)的意義。C方法意義和A、B方法是一樣的,都是使用當(dāng)前實(shí)例作為對(duì)象鎖。所以我們盡量避免這樣使用。可以參考下面的做法。
public class Demo { private byte[] lock1 = new byte[0];//java中生成一個(gè)0長度的byte數(shù)組比生成一個(gè)普通的Object容易; private byte[] lock2 = new byte[0]; public void synchronize A() { //do something... } public void B() { synchronized(lock) { //do something... } } public void C() { synchronized(lock2){ //do something } } }
除此之外,我們還要注意死鎖的情況。在JAVA環(huán)境下 ReentrantLock和synchronized都是可重入鎖,一定情況下避免了死鎖。詳情請(qǐng)參考可重入鎖
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70189.html
摘要:導(dǎo)入包注意使用了關(guān)鍵字上面使用了關(guān)鍵字之后,可以直接使用中的方法。通過關(guān)鍵字調(diào)用有參構(gòu)造方法控制臺(tái)輸出總結(jié)屬性和方法可以再?zèng)]有實(shí)例化對(duì)象的時(shí)候直接由類名稱進(jìn)行調(diào)用。屬性保存在全局?jǐn)?shù)據(jù)區(qū)。 前面兩篇分別介紹了static的屬性以及方法。本篇就做一個(gè)收尾,介紹下剩下的。 在之前的總結(jié): 不管多少個(gè)對(duì)象,都使用同一個(gè) static 屬性 使用 static 方法可以避免掉用實(shí)例化方法之后才...
摘要:強(qiáng)制類型轉(zhuǎn)換下標(biāo)運(yùn)算符變量與常量常量是在程序中的不會(huì)變化的數(shù)據(jù)變量其實(shí)就是內(nèi)存中的一個(gè)存儲(chǔ)空間,用于存儲(chǔ)數(shù)據(jù)。表示結(jié)束本次循環(huán),繼續(xù)下次循環(huán)。 Java知識(shí)點(diǎn)總結(jié) (基本語法) @(Java知識(shí)點(diǎn)總結(jié))[Java, Java基本語法] @(Java開發(fā))[Java基本語法] [toc] Java特點(diǎn) 簡(jiǎn)單自然平臺(tái)可移植性支持函數(shù)式編程JIT 編譯更好的并發(fā)編程健壯安全 執(zhí)行方式 編譯...
摘要:知識(shí)點(diǎn)總結(jié)面向?qū)ο笾R(shí)點(diǎn)總結(jié)面向?qū)ο竺嫦驅(qū)ο蟾拍钍窍鄬?duì)于面向過程而言,過程其實(shí)就是函數(shù),對(duì)象是將函數(shù)和屬性進(jìn)行了封裝。指向了該對(duì)象關(guān)鍵字代表對(duì)象。靜態(tài)變量所屬于類,所以也稱為類變量成員變量存在于堆內(nèi)存中。 Java知識(shí)點(diǎn)總結(jié)(面向?qū)ο螅?@(Java知識(shí)點(diǎn)總結(jié))[Java, Java面向?qū)ο骫 [toc] 面向?qū)ο蟾拍?是相對(duì)于面向過程而言,過程其實(shí)就是函數(shù),對(duì)象是將函數(shù)和屬性進(jìn)行了封...
摘要:無論是互斥鎖,還是自旋鎖,在任何時(shí)刻,最多只能有一個(gè)保持者,也就說,在任何時(shí)刻最多只能有一個(gè)執(zhí)行單元獲得鎖。另外在中引入了自適應(yīng)的自旋鎖。和關(guān)鍵字的總結(jié)推薦一 該文已加入開源文檔:JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識(shí))。地址:https://github.com/Snailclimb... 本文是對(duì) synchronized 關(guān)鍵字使用、底層原理、JD...
摘要:再附一部分架構(gòu)面試視頻講解本文已被開源項(xiàng)目學(xué)習(xí)筆記總結(jié)移動(dòng)架構(gòu)視頻大廠面試真題項(xiàng)目實(shí)戰(zhàn)源碼收錄 Java反射(一)Java反射(二)Java反射(三)Java注解Java IO(一)Java IO(二)RandomAccessFileJava NIOJava異常詳解Java抽象類和接口的區(qū)別Java深拷貝和淺拷...
摘要:基礎(chǔ)問題的的性能及原理之區(qū)別詳解備忘筆記深入理解流水線抽象關(guān)鍵字修飾符知識(shí)點(diǎn)總結(jié)必看篇中的關(guān)鍵字解析回調(diào)機(jī)制解讀抽象類與三大特征時(shí)間和時(shí)間戳的相互轉(zhuǎn)換為什么要使用內(nèi)部類對(duì)象鎖和類鎖的區(qū)別,,優(yōu)缺點(diǎn)及比較提高篇八詳解內(nèi)部類單例模式和 Java基礎(chǔ)問題 String的+的性能及原理 java之yield(),sleep(),wait()區(qū)別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 3210·2021-11-24 10:30
閱讀 1324·2021-09-30 09:56
閱讀 2396·2021-09-07 10:20
閱讀 2610·2021-08-27 13:10
閱讀 712·2019-08-30 11:11
閱讀 2064·2019-08-29 12:13
閱讀 769·2019-08-26 12:24
閱讀 2911·2019-08-26 12:20