java synchronized關(guān)鍵字的用法
0.先導(dǎo)的問題代碼
下面的代碼演示了一個(gè)計(jì)數(shù)器,兩個(gè)線程同時(shí)對i進(jìn)行累加的操作,各執(zhí)行1000000次.我們期望的結(jié)果肯定是i=2000000.但是我們多次執(zhí)行以后,會發(fā)現(xiàn)i的值永遠(yuǎn)小于2000000.這是因?yàn)?兩個(gè)線程同時(shí)對i進(jìn)行寫入的時(shí)候,其中一個(gè)線程的結(jié)果會覆蓋另外一個(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è)問題,我們必須保證多個(gè)線程在對i進(jìn)行操作的時(shí)候,要完全的同步.也就是說到A線程對i進(jìn)行寫入的時(shí)候,B線程不僅不可以寫入,連讀取都不可以.
1.synchronized關(guān)鍵字的作用
關(guān)鍵字synchronized的作用其實(shí)就是實(shí)現(xiàn)線程間的同步.它的工作就是對同步的代碼進(jìn)行加鎖,使得每一次,只能有一個(gè)線程進(jìn)入同步塊,從而保證線程間的安全性.就像上面的代碼中,i++的操作只能同時(shí)又一個(gè)線程在執(zhí)行.
2.synchronized關(guān)鍵字的用法
指定對象加鎖:對給定的對象進(jìn)行加鎖,進(jìn)入同步代碼塊要獲得給定對象的鎖
直接作用于實(shí)例方法:相當(dāng)于對當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼塊要獲得當(dāng)前實(shí)例的鎖(這要求創(chuàng)建Thread的時(shí)候,要用同一個(gè)Runnable的實(shí)例才可以)
直接作用于靜態(tài)方法:相當(dāng)于給當(dāng)前類加鎖,進(jìn)入同步代碼塊前要獲得當(dāng)前類的鎖
2.1指定對象加鎖
下面的代碼,將synchronized作用于一個(gè)給定的對象.這里有一個(gè)注意的,給定對象一定要是static的,否則我們每次new一個(gè)線程出來,彼此并不共享該對象,加鎖的意義也就不存在了.
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í)例方法,就是說在進(jìn)入increase()方法之前,線程必須獲得當(dāng)前實(shí)例的鎖.這就要求我們,在創(chuàng)建Thread實(shí)例的時(shí)候,要使用同一個(gè)Runnable的對象實(shí)例.否則,線程的鎖都不在同一個(gè)實(shí)例上面,無從去談加鎖/同步的問題了.
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);
}
}
請注意main方法的前三行,說明關(guān)鍵字作用于實(shí)例方法上的正確用法.
2.3直接作用于靜態(tài)方法
將synchronized關(guān)鍵字作用在static方法上,就不用像上面的例子中,兩個(gè)線程要指向同一個(gè)Runnable方法.因?yàn)榉椒▔K需要請求的是當(dāng)前類的鎖,而不是當(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ù)的正確性,我們自然會需要對計(jì)數(shù)器加鎖,因此,我們可能會寫出下面的代碼:
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í)候,會發(fā)現(xiàn)輸出的i很小.這說明線程并沒有安全.
要解釋這個(gè)問題,要從Integer說起:在Java中,Integer屬于不變對象,和String一樣,對象一旦被創(chuàng)建,就不能被修改了.如果你有一個(gè)Integer=1,那么它就永遠(yuǎn)都是1.如果你想讓這個(gè)對象=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è)工廠方法,他會傾向于返回一個(gè)新的Integer對象,并把值重新復(fù)制給i;
所以,我們就知道問題所在的原因,由于在多個(gè)線程之間,由于i++之后,i都指向了一個(gè)新的對象,所以線程每次加鎖可能都加載了不同的對象實(shí)例上面.解決方法很簡單,使用上面的3種synchronize的方法之一就可以解決了.
- 深入理解java中的synchronized關(guān)鍵字
- 詳解Java中synchronized關(guān)鍵字的死鎖和內(nèi)存占用問題
- Java中synchronized關(guān)鍵字修飾方法同步的用法詳解
- java多線程編程之使用Synchronized關(guān)鍵字同步類方法
- Java關(guān)鍵字volatile和synchronized作用和區(qū)別
- 舉例講解Java中synchronized關(guān)鍵字的用法
- 實(shí)例解析Java中的synchronized關(guān)鍵字與線程安全問題
- 深入了解Java中Synchronized關(guān)鍵字的實(shí)現(xiàn)原理
- java基本教程之synchronized關(guān)鍵字 java多線程教程
- Java中synchronized關(guān)鍵字鎖的力度與位置示例詳解
相關(guān)文章
Java實(shí)現(xiàn)AWT四大事件的詳細(xì)過程
AWT的事件處理是一種委派式事件處理方式:普通組件(事件源)將整個(gè)事件處理委托給特定的對象(事件監(jiān)聽器);當(dāng)該事件源發(fā)生指定的事件時(shí),就通知所委托的事件監(jiān)聽器,由事件監(jiān)聽器來處理這個(gè)事件2022-04-04
SpringBoot實(shí)現(xiàn)日志鏈路追蹤的項(xiàng)目實(shí)踐
在分布式系統(tǒng)中,由于請求的處理過程可能會跨越多個(gè)服務(wù),因此,對請求的追蹤變得尤為重要,本文主要介紹了SpringBoot實(shí)現(xiàn)日志鏈路追蹤的項(xiàng)目實(shí)踐,感興趣的可以了解一下2024-03-03
零基礎(chǔ)寫Java知乎爬蟲之將抓取的內(nèi)容存儲到本地
上一回我們說到了如何把知乎的某些內(nèi)容爬取出來,那么這一回我們就說說怎么把這些內(nèi)容存儲到本地吧。2014-11-11
java easyPOI實(shí)現(xiàn)導(dǎo)出一對多數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了java如何利用easyPOI實(shí)現(xiàn)導(dǎo)出一對多數(shù)據(jù),并且可以設(shè)置邊框、字體和字體大小,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12
Spring cloud gateway設(shè)置context-path服務(wù)路由404排查過程
這篇文章主要介紹了Spring cloud gateway設(shè)置context-path服務(wù)路由404排查過程,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
git stash 和unstash的使用操作,git unstash failed
這篇文章主要介紹了git stash 和unstash的使用操作,git unstash failed,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
IDEA創(chuàng)建javaee項(xiàng)目依賴war exploded變紅失效的解決方案
在使用IntelliJ IDEA創(chuàng)建JavaEE項(xiàng)目時(shí),可能會遇到Tomcat部署的warexploded文件出現(xiàn)問題,解決方法是首先刪除有問題的warexploded依賴,然后根據(jù)圖示重新導(dǎo)入項(xiàng)目,此外,調(diào)整虛擬路徑有時(shí)也能有效解決問題2024-09-09

