java synchronized關(guān)鍵字的用法
0.先導(dǎo)的問(wèn)題代碼
下面的代碼演示了一個(gè)計(jì)數(shù)器,兩個(gè)線程同時(shí)對(duì)i進(jìn)行累加的操作,各執(zhí)行1000000次.我們期望的結(jié)果肯定是i=2000000.但是我們多次執(zhí)行以后,會(huì)發(fā)現(xiàn)i的值永遠(yuǎn)小于2000000.這是因?yàn)?兩個(gè)線程同時(shí)對(duì)i進(jìn)行寫(xiě)入的時(shí)候,其中一個(gè)線程的結(jié)果會(huì)覆蓋另外一個(gè).
public class AccountingSync implements Runnable { static int i = 0; public void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
要從根本上解決這個(gè)問(wèn)題,我們必須保證多個(gè)線程在對(duì)i進(jìn)行操作的時(shí)候,要完全的同步.也就是說(shuō)到A線程對(duì)i進(jìn)行寫(xiě)入的時(shí)候,B線程不僅不可以寫(xiě)入,連讀取都不可以.
1.synchronized關(guān)鍵字的作用
關(guān)鍵字synchronized的作用其實(shí)就是實(shí)現(xiàn)線程間的同步.它的工作就是對(duì)同步的代碼進(jìn)行加鎖,使得每一次,只能有一個(gè)線程進(jìn)入同步塊,從而保證線程間的安全性.就像上面的代碼中,i++的操作只能同時(shí)又一個(gè)線程在執(zhí)行.
2.synchronized關(guān)鍵字的用法
指定對(duì)象加鎖:對(duì)給定的對(duì)象進(jìn)行加鎖,進(jìn)入同步代碼塊要獲得給定對(duì)象的鎖
直接作用于實(shí)例方法:相當(dāng)于對(duì)當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼塊要獲得當(dāng)前實(shí)例的鎖(這要求創(chuàng)建Thread的時(shí)候,要用同一個(gè)Runnable的實(shí)例才可以)
直接作用于靜態(tài)方法:相當(dāng)于給當(dāng)前類(lèi)加鎖,進(jìn)入同步代碼塊前要獲得當(dāng)前類(lèi)的鎖
2.1指定對(duì)象加鎖
下面的代碼,將synchronized作用于一個(gè)給定的對(duì)象.這里有一個(gè)注意的,給定對(duì)象一定要是static的,否則我們每次new一個(gè)線程出來(lái),彼此并不共享該對(duì)象,加鎖的意義也就不存在了.
public class AccountingSync implements Runnable {
final static Object OBJECT = new Object();
static int i = 0;
public void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
synchronized (OBJECT) {
increase();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new AccountingSync());
Thread t2 = new Thread(new AccountingSync());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
synchronized關(guān)鍵字作用于實(shí)例方法,就是說(shuō)在進(jìn)入increase()方法之前,線程必須獲得當(dāng)前實(shí)例的鎖.這就要求我們,在創(chuàng)建Thread實(shí)例的時(shí)候,要使用同一個(gè)Runnable的對(duì)象實(shí)例.否則,線程的鎖都不在同一個(gè)實(shí)例上面,無(wú)從去談加鎖/同步的問(wèn)題了.
public class AccountingSync implements Runnable { static int i = 0; public synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync accountingSync = new AccountingSync(); Thread t1 = new Thread(accountingSync); Thread t2 = new Thread(accountingSync); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
請(qǐng)注意main方法的前三行,說(shuō)明關(guān)鍵字作用于實(shí)例方法上的正確用法.
2.3直接作用于靜態(tài)方法
將synchronized關(guān)鍵字作用在static方法上,就不用像上面的例子中,兩個(gè)線程要指向同一個(gè)Runnable方法.因?yàn)榉椒▔K需要請(qǐng)求的是當(dāng)前類(lèi)的鎖,而不是當(dāng)前實(shí)例,線程間還是可以正確同步的.
public class AccountingSync implements Runnable { static int i = 0; public static synchronized void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AccountingSync()); Thread t2 = new Thread(new AccountingSync()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
3.錯(cuò)誤的加鎖
從上面的例子里,我們知道,如果我們需要一個(gè)計(jì)數(shù)器應(yīng)用,為了保證數(shù)據(jù)的正確性,我們自然會(huì)需要對(duì)計(jì)數(shù)器加鎖,因此,我們可能會(huì)寫(xiě)出下面的代碼:
public class BadLockOnInteger implements Runnable { static Integer i = 0; @Override public void run() { for (int j = 0; j < 1000000; j++) { synchronized (i) { i++; } } } public static void main(String[] args) throws InterruptedException { BadLockOnInteger badLockOnInteger = new BadLockOnInteger(); Thread t1 = new Thread(badLockOnInteger); Thread t2 = new Thread(badLockOnInteger); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
當(dāng)我們運(yùn)行上面代碼的時(shí)候,會(huì)發(fā)現(xiàn)輸出的i很小.這說(shuō)明線程并沒(méi)有安全.
要解釋這個(gè)問(wèn)題,要從Integer說(shuō)起:在Java中,Integer屬于不變對(duì)象,和String一樣,對(duì)象一旦被創(chuàng)建,就不能被修改了.如果你有一個(gè)Integer=1,那么它就永遠(yuǎn)都是1.如果你想讓這個(gè)對(duì)象=2呢?只能重新創(chuàng)建一個(gè)Integer.每次i++之后,相當(dāng)于調(diào)用了Integer的valueOf方法,我們看一下Integer的valueOf方法的源碼:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
Integer.valueOf()實(shí)際上是一個(gè)工廠方法,他會(huì)傾向于返回一個(gè)新的Integer對(duì)象,并把值重新復(fù)制給i;
所以,我們就知道問(wèn)題所在的原因,由于在多個(gè)線程之間,由于i++之后,i都指向了一個(gè)新的對(duì)象,所以線程每次加鎖可能都加載了不同的對(duì)象實(shí)例上面.解決方法很簡(jiǎn)單,使用上面的3種synchronize的方法之一就可以解決了.
- 深入理解java中的synchronized關(guān)鍵字
- 詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問(wèn)題
- JAVA面試題 簡(jiǎn)談你對(duì)synchronized關(guān)鍵字的理解
- Java中使用synchronized關(guān)鍵字實(shí)現(xiàn)簡(jiǎn)單同步操作示例
- 舉例講解Java中synchronized關(guān)鍵字的用法
- 實(shí)例解析Java中的synchronized關(guān)鍵字與線程安全問(wèn)題
- Java中復(fù)雜的Synchronized關(guān)鍵字使用方法詳解
相關(guān)文章
spring boot security設(shè)置忽略地址不生效的解決
這篇文章主要介紹了spring boot security設(shè)置忽略地址不生效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Spring動(dòng)態(tài)配置計(jì)時(shí)器觸發(fā)時(shí)間的實(shí)例代碼
這篇文章主要介紹了Spring動(dòng)態(tài)配置計(jì)時(shí)器觸發(fā)時(shí)間的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06SpringBoot使用Redis實(shí)現(xiàn)分布式緩存
這篇文章主要介紹了SpringBoot redis分布式緩存實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼解析的非常詳細(xì),感興趣的同學(xué)可以參考閱讀2023-04-04Java之SpringCloud Eurka注冊(cè)錯(cuò)誤解決方案
這篇文章主要介紹了Java之SpringCloud Eurka注冊(cè)錯(cuò)誤解決方案,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Spring Cloud Gateway 內(nèi)存溢出的解決方案
這篇文章主要介紹了Spring Cloud Gateway 內(nèi)存溢出的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot項(xiàng)目集成xxljob實(shí)現(xiàn)全紀(jì)錄
XXL-JOB是一個(gè)分布式任務(wù)調(diào)度平臺(tái),本文主要介紹了SpringBoot項(xiàng)目集成xxljob實(shí)現(xiàn)全紀(jì)錄,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11淺談Java高并發(fā)解決方案以及高負(fù)載優(yōu)化方法
這篇文章主要介紹了淺談Java高并發(fā)解決方案以及高負(fù)載優(yōu)化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08rocketmq的AclClientRPCHook權(quán)限控制使用技巧示例詳解
這篇文章主要為大家介紹了rocketmq的AclClientRPCHook使用技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Alibaba?Nacos配置中心動(dòng)態(tài)感知原理示例解析
這篇文章主要介紹了Alibaba?Nacos配置中心動(dòng)態(tài)感知原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08