java中的同步鎖包括對象鎖和類鎖锐锣。
對象鎖: 針對的是具體的對象實(shí)例腌闯;類鎖:針對的是整個(gè)class類
現(xiàn)在先讓我們看一些文縐縐的書本概念,暫時(shí)看不懂沒關(guān)系雕憔,只要各位看官耐心看完這篇文章的姿骏,就都明白了
- 多線程之間的同步實(shí)際上是靠鎖來實(shí)現(xiàn)的
- 在java的運(yùn)行時(shí)環(huán)境中(即JVM虛擬機(jī)中),會被多線程共享的數(shù)據(jù)包括
堆中的實(shí)例變量
和方法區(qū)中的類變量
(可以關(guān)注我的簡書號斤彼,后續(xù)會有相關(guān)白話文章介紹JVM內(nèi)存管理)
在JVM中分瘦,每個(gè)對象和類在邏輯上都與一個(gè)監(jiān)視器相關(guān)聯(lián),這是JVM內(nèi)部實(shí)現(xiàn)的琉苇,我們不需要關(guān)心嘲玫。我們只需要記住以下兩句話
1. 對于對象,監(jiān)視器保護(hù)的是對象的 實(shí)例變量
(對象鎖)
2. 對于類并扇,監(jiān)視器保護(hù)的是類的 類變量
(類鎖)
這里多說一句去团,實(shí)際上類鎖也是根據(jù)對象鎖來實(shí)現(xiàn)的,具體不再展開介紹穷蛹,以后會專門寫文章介紹土陪。
對于類鎖,JVM在load class文件時(shí)俩莽,會創(chuàng)建一個(gè)與該class相關(guān)聯(lián)的java.lang.Class類的實(shí)例,當(dāng)鎖住這個(gè)class類關(guān)聯(lián)的java.lang.Class實(shí)例的時(shí)候乔遮,實(shí)際上就鎖住了這個(gè)類的class類
對于每一個(gè)對象
一個(gè)線程可以多次對同一個(gè)對象上鎖
扮超,JVM維護(hù)一個(gè)加鎖計(jì)數(shù)器,線程每獲得一次該對象,計(jì)數(shù)器就加1出刷,每釋放一次璧疗,計(jì)數(shù)器就減 1,當(dāng)計(jì)數(shù)器值為0時(shí)馁龟,鎖就被完全釋放了
我們無需去手動(dòng)進(jìn)行加鎖 釋放鎖崩侠,這些工作JVM都替我們完成了
看到這里,大家應(yīng)該能明白坷檩,其實(shí)我們thread線程中每訪問一個(gè)實(shí)例却音,內(nèi)部其實(shí)都有加鎖 釋放鎖的動(dòng)作。
大家或許有一個(gè)疑問矢炼,那我的變量或代碼(即所謂的臨界區(qū))是怎么實(shí)現(xiàn)多線程安全訪問呢系瓢? 也就是說現(xiàn)在各個(gè)線程都拿到鎖了,但是怎么去讓這個(gè)鎖生效 呢句灌?接下來就是我們的主角synchronized
上場了
synchronized關(guān)鍵字
通過上述的分析夷陋,我們知道(線程)鎖上鎖的對象了,那么怎么上鎖呢胰锌,實(shí)際就是通過這個(gè)synchronized關(guān)鍵字進(jìn)行上鎖骗绕。
synchronized 之所以可以實(shí)現(xiàn)同步还蹲,是因?yàn)閖ava中的每一個(gè)對象都會作為鎖琴昆,即java中的每一個(gè)對象JVM在內(nèi)部都為其關(guān)聯(lián)了一個(gè)監(jiān)控器(即加了鎖)
synchronized的作用域包括以下三個(gè)
- 普通同步方法,鎖針對的是當(dāng)前實(shí)例對象
- 靜態(tài)同步方法隙畜,鎖針對的是當(dāng)前class對象
- 同步方法塊榛搔,鎖的是括號里面的對象(synchrozed(this))
如果小伙伴們還是覺得不太好理解诺凡,下面我們通過幾個(gè)測試?yán)觼砜匆幌?/p>
package ke.com;
/**
* Created by zl on 2019/4/30.
*/
public class ShareResource implements Runnable {
static int i = 0;
synchronized public void increase() {
i = i+1;
}
@Override
public void run() {
for (int k=0; k<5000;k++) {
increase();
}
}
public static void main(String[] args) throws InterruptedException {
ShareResource instance1 = new ShareResource();
ShareResource instance2 = new ShareResource();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
// join的含義是等兩個(gè)子線程都執(zhí)行完畢后,主線程在執(zhí)行打印代碼
thread1.join();
thread2.join();
System.out.println("i=" + i);
}
}
上面的代碼執(zhí)行結(jié)果是下圖所示:
不是我們想象的10000践惑。
這是因?yàn)槲覀兊膍ain方法中生成了兩個(gè)ShareResource實(shí)例腹泌,即我們獲取的是兩把鎖。所以我們應(yīng)該用類鎖尔觉,而不應(yīng)該用對象鎖(即我們所在了方法上)凉袱,而對象鎖是針對某一個(gè)具體實(shí)例的
我們把測試代碼改成如下,對靜態(tài)方法的synchronized就是對類的synchronized侦铜,這樣我們獲取瑣時(shí)专甩,獲取的就是類鎖
// 只需要把increate方法改成靜態(tài)的
synchronized public static void increase() {
i = i+1;
}
關(guān)于對象鎖,這里多說一句钉稍,假如 有synchronized method1 和synchronized method2 涤躲,同步了多個(gè)方法,method1的鎖 不會造成 method2訪問線程的阻塞贡未,即兩者的鎖不會相互影響种樱。
另外蒙袍,同步也不會被繼承
說完了普通方法同步和靜態(tài)方法的同步,我們來看一下同步方法塊
同步方法塊
同步代碼塊又分為 synchronized(this)和synchronized(非this)嫩挤,下面我們通過具體代碼來看一下兩者的區(qū)別
package ke.com;
/**
* Created by zl on 2019/4/30.
*/
public class SynchronizedThisTest {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.start();
ThreadB b = new ThreadB(service);
b.start();
}
}
class Service {
public void serviceA() {
synchronized (this) {
System.out.println("業(yè)務(wù)A開始時(shí)間="+System.currentTimeMillis());
// 讓調(diào)用線程休眠2s讓出cpu
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("業(yè)務(wù)A結(jié)束時(shí)間="+System.currentTimeMillis());
}
}
public void serviceB() {
synchronized (this) {
System.out.println("業(yè)務(wù)B的開始時(shí)間="+System.currentTimeMillis());
System.out.println("業(yè)務(wù)B的結(jié)束時(shí)間="+System.currentTimeMillis());
}
}
}
class ThreadA extends Thread {
private Service service;
ThreadA(Service s) {
super();
this.service = s;
}
@Override
public void run() {
super.run();
// 調(diào)用業(yè)務(wù)A
service.serviceA();
}
}
class ThreadB extends Thread {
private Service service;
ThreadB(Service s) {
this.service = s;
}
@Override
public void run() {
super.run();
service.serviceB();
}
}
上面代碼的運(yùn)行結(jié)果是可見就算業(yè)務(wù)A的線程的時(shí)間很長害幅,B線程也不會獲取執(zhí)行機(jī)會,說明synchronized(this)是鎖的整個(gè)對象自身
下面我們換成synchronized(object)的形式看看
我們把Service類重寫
class Service {
public void serviceA() {
Object o = new Object();
synchronized (o) {
System.out.println("業(yè)務(wù)A開始時(shí)間="+System.currentTimeMillis());
// 讓調(diào)用線程休眠2s讓出cpu
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("業(yè)務(wù)A結(jié)束時(shí)間="+System.currentTimeMillis());
}
}
public void serviceB() {
Object o = new Object();
synchronized (o) {
System.out.println("業(yè)務(wù)B的開始時(shí)間="+System.currentTimeMillis());
System.out.println("業(yè)務(wù)B的結(jié)束時(shí)間="+System.currentTimeMillis());
}
}
}
運(yùn)行結(jié)果如下所以在實(shí)際應(yīng)用中一般不用synchronized(this) 而是用一個(gè)synchronized(object)產(chǎn)生兩個(gè)鎖岂昭,來提高執(zhí)行效率
Object中的wait(),notify(),notifyAll()方法一般與synchronized同步鎖綁定使用