Java中關(guān)鍵字synchronized的使用方法詳解
synchronized是Java里的一個(gè)關(guān)鍵字,起到的一個(gè)效果是“監(jiān)視器鎖”~~,它的功能就是保證操作的原子性,同時(shí)禁止指令重排序和保證內(nèi)存的可見性!
public class TestDemo {
static class Counter{
public int count = 0;
public void add(){
count++;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
counter.add();
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
counter.add();
}
}
};
//啟動(dòng)兩個(gè)線程
t1.start();
t2.start();
//等待兩個(gè)線程結(jié)束
t1.join();
t2.join();
System.out.println(counter.count);
}
}

此時(shí)的線程就是不安全的,如何解決呢?
給我們的Counter對(duì)象里的add方法加上synchronized關(guān)鍵字,針對(duì)這個(gè)方法進(jìn)行了加鎖操作。進(jìn)入代碼塊(調(diào)用方法)自動(dòng)加鎖,出了代碼塊(方法結(jié)束),自動(dòng)解鎖。
public class TestDemo {
static class Counter{
public int count = 0;
//修飾方法
synchronized public void add{
count++;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
counter.add();
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
counter.add();
}
}
};
//啟動(dòng)兩個(gè)線程
t1.start();
t2.start();
//等待兩個(gè)線程結(jié)束
t1.join();
t2.join();
System.out.println(counter.count);
}
}

那么這里的代碼是如何保證正確的呢?
使用synchronized 就相當(dāng)于在我們執(zhí)行的指令里又加入了2條新指令。
LOCK (加鎖)
UNLOCK (解鎖)
LOCK操作特性:只有一個(gè)線程能執(zhí)行成功!如果第一個(gè)線程執(zhí)行成功,第二個(gè)線程也嘗試LOCK,就會(huì)阻塞等待,直到第一個(gè)線程執(zhí)行UNLOCK 釋放鎖~

通過LOCK和UNLOCK 就把 LOAD ADD SAVE 這三個(gè)指令,給打包成了一個(gè)原子的操作(中間不能被打斷,也不能被其他線程穿插)。
這里的加鎖也是保證原子性的核心操作,所以線程里的沒組指令就會(huì)順序執(zhí)行,不在穿插執(zhí)行,就保證了線程1執(zhí)行完之后再去執(zhí)行線程2。
舉個(gè)例子:
就好比張三和李四去ATM里去取錢,當(dāng)張三進(jìn)去取錢時(shí),進(jìn)去后就會(huì)鎖門,李四就會(huì)在外面等待,直到張三取完錢出來后,李四在進(jìn)去取錢。
synchronized 也會(huì)禁止編譯器進(jìn)行“內(nèi)存可見性”和“指令重排序”的優(yōu)化~ 同時(shí)程序運(yùn)行的效率就會(huì)降低,
也會(huì)導(dǎo)致線程之間相互去等待,就涉及到系統(tǒng)的一些調(diào)度,也會(huì)引入一些時(shí)間成本。
synchronized修飾的對(duì)象有以下幾種:
修飾一個(gè)代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號(hào){}括起來的代碼,作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象;
public class TestDemo{
public void methond() {
// 進(jìn)入代碼塊會(huì)鎖 this 指向?qū)ο笾械逆i;
// 出代碼塊會(huì)釋放 this 指向的對(duì)象中的鎖
synchronized (this) {
}
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
demo.methond();
}
}
修飾一個(gè)方法,被修飾的方法稱為同步方法,其作用的范圍是整個(gè)方法,作用的對(duì)象是調(diào)用這個(gè)方法的對(duì)象;
public class TestDemo{
public synchronized void methond() {
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
demo.methond();
// 進(jìn)入方法會(huì)鎖 demo 指向?qū)ο笾械逆i;
// 出方法會(huì)釋放 demo 指向的對(duì)象中的鎖
}
}
修改一個(gè)靜態(tài)的方法,其作用的范圍是整個(gè)靜態(tài)方法,作用的對(duì)象是這個(gè)類的所有對(duì)象;
public class TestDemo{
public synchronized static void methond() {
}
public static void main(String[] args) {
methond();
// 進(jìn)入方法會(huì)鎖 TestDemo.class 指向?qū)ο笾械逆i;
//出方法會(huì)釋放 TestDemo.class 指向的對(duì)象中的鎖
}
}
修改一個(gè)類,其作用的范圍是synchronized后面括號(hào)括起來的部分,作用主的對(duì)象是這個(gè)類的所有對(duì)象。
public class TestDemo{
public static void methond() {
// 進(jìn)入代碼塊會(huì)鎖 TestDemo.class 指向?qū)ο笾械逆i;
//出代碼塊會(huì)釋放 TestDemo.class 指向的對(duì)象中的鎖
synchronized (TestDemo.class) {
}
}
public static void main(String[] args) {
TestDemo demo = new TestDemo();
demo.methond();
}
}
總結(jié):
- 無論synchronized關(guān)鍵字加在方法上還是對(duì)象上,如果它作用的對(duì)象是非靜態(tài)的,則它取得的鎖是對(duì)象;
- 如果synchronized作用的對(duì)象是一個(gè)靜態(tài)方法或一個(gè)類,則它取得的鎖是對(duì)類,該類所有的對(duì)象同一把鎖。
- 每個(gè)對(duì)象只有一個(gè)鎖(lock)與之相關(guān)聯(lián),誰拿到這個(gè)鎖誰就可以運(yùn)行它所控制的那段代碼。
- 實(shí)現(xiàn)同步是要很大的系統(tǒng)開銷作為代價(jià)的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。
拓展:
public class TestDemo {
static class Counter{
public int count = 0;
public void add(){
synchronized (this){
count++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
synchronized (counter){
counter.add();
}
}
}
};
Thread t2 = new Thread(){
@Override
public void run() {
for (int i = 0; i < 50000; i++) {
synchronized (counter){
counter.add();
}
}
}
};
//啟動(dòng)兩個(gè)線程
t1.start();
t2.start();
//等待兩個(gè)線程結(jié)束
t1.join();
t2.join();
System.out.println(counter.count);
}
}
此時(shí)可以看出上述代碼,加了兩次鎖,會(huì)發(fā)生什么呢?


但是運(yùn)行代碼發(fā)現(xiàn)程序依然正確運(yùn)行?? 為什么
但是上述分析死鎖的思路是對(duì)的
只是因?yàn)閟ynchronized內(nèi)部使用特殊手段來處理了這種情況 。
這樣的操作特性我們叫做 可重入鎖
synchronized 內(nèi)部記錄了當(dāng)前這把鎖是哪個(gè)線程持有的~
如果當(dāng)前加鎖線程和持有鎖的線程是同一個(gè)線程~
此時(shí)就并不是真的進(jìn)行“加鎖操作”,而是把一個(gè)計(jì)數(shù)器加一;
如果后續(xù)該線程繼續(xù)嘗試獲取鎖,繼續(xù)判定加鎖線程和持有鎖線程是不是同一個(gè)線程,只要是同一個(gè)線程,就不真的加鎖,而是計(jì)數(shù)器+1;
如果該線程調(diào)用解鎖操作,也不是立刻就解鎖,而是計(jì)數(shù)器減1
直到計(jì)數(shù)器減成0了,才認(rèn)為真的要“釋放鎖”,才允許其他線程來獲取鎖~~
總結(jié)
到此這篇關(guān)于Java中synchronized使用的文章就介紹到這了,更多相關(guān)Java中synchronized用法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
本地啟動(dòng)RocketMQ未映射主機(jī)名產(chǎn)生的超時(shí)問題最新解決方案
這篇文章主要介紹了本地啟動(dòng)RocketMQ未映射主機(jī)名產(chǎn)生的超時(shí)問題,本文給大家分享最新解決方案,感興趣的朋友跟隨小編一起看看吧2024-02-02
詳解Spring Security如何在權(quán)限中使用通配符
小伙伴們知道,在Shiro中,默認(rèn)是支持權(quán)限通配符的。現(xiàn)在給用戶授權(quán)的時(shí)候,可以一個(gè)權(quán)限一個(gè)權(quán)限的配置,也可以直接用通配符。本文將介紹Spring Security如何在權(quán)限中使用通配符,需要的可以參考一下2022-06-06
java解決單緩沖生產(chǎn)者消費(fèi)者問題示例
這篇文章主要介紹了java解單緩沖生產(chǎn)者消費(fèi)者問題示例,需要的朋友可以參考下2014-04-04
Springboot項(xiàng)目接口限流實(shí)現(xiàn)方案
這篇文章主要介紹了Springboot項(xiàng)目接口限流實(shí)現(xiàn)方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Sprigmvc項(xiàng)目轉(zhuǎn)為springboot的方法
本篇文章主要介紹了Sprigmvc項(xiàng)目轉(zhuǎn)為springboot的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02
SpringBoot異步實(shí)現(xiàn)的8種方式
異步執(zhí)行對(duì)于開發(fā)者來說并不陌生,在實(shí)際的開發(fā)過程中,很多場(chǎng)景多會(huì)使用到異步,本文主要介紹了SpringBoot異步實(shí)現(xiàn)的8種方式,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09

