今天我們將通過幾個場景來深刻的理解幾個問題。
如下:
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendMsm();
},"A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{ //資源類
public synchronized void sendMsm(){
System.out.println("傳簡訊");
}
public synchronized void call(){
System.out.println("打電話");
}
}
簡單說明一下:一個資源類裡面有兩個同步方法(傳簡訊 和 打電話),然後主執行緒建立了一個資源類物件,並開了兩個執行緒去分別執行這個物件的兩個同步方法。
執行結果:
傳簡訊
打電話
分析:
執行緒A先拿到了 phone 這個物件的鎖,所以他先執行。
這裡 不 要認為是主執行緒先呼叫了A執行緒所以他先執行。
不理解的話,我們再看一個場景
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendMsm() throws InterruptedException {
//等待3秒鐘
TimeUnit.SECONDS.sleep(3);
System.out.println("傳簡訊");
}
public synchronized void call(){
System.out.println("打電話");
}
}
我們讓 「傳簡訊」方法 持續 3 秒鐘。
執行結果:
傳簡訊
打電話
分析:
結果還是先 傳簡訊 後 打電話,就像我 剛剛說的一樣,傳簡訊是持續3秒,並不影響他先得到鎖,因為主執行緒過了一秒才去呼叫執行緒B,所以執行緒A 99.9%的機率是先拿到鎖的。
synchronized 鎖的物件是方法的呼叫者。
執行緒A和B 都是呼叫的同一個物件(phone)的方法,所以他們都是去競爭同一把鎖。誰先拿到鎖誰先執行,沒拿到就得等。
如果沒有鎖競爭關係,那麼應該就是拼的速度了。
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone{
public synchronized void sendMsm() throws InterruptedException {
//等待3秒鐘
TimeUnit.SECONDS.sleep(3);
System.out.println("傳簡訊");
}
public synchronized void call(){
System.out.println("打電話");
}
public void hello(){
System.out.println("Hello World!");
}
}
這次我們讓執行緒B呼叫 物件phone的 hello()方法,注意:hello()方法不是同步方法!!
執行結果:
Hello World!
傳簡訊
分析:
因為 hello()方法不是同步方法,所以壓根就沒有鎖競爭這一說,雖然 執行緒B 比 執行緒A 晚啟動了一秒鐘,但 「傳簡訊」 要等三秒鐘才列印,這樣一來,Hello World!還會比 「傳簡訊」 先列印 2 秒鐘。
同樣沒有鎖競爭的場景
如下:
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone1.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsm() throws InterruptedException {
//等待3秒鐘
TimeUnit.SECONDS.sleep(3);
System.out.println("傳簡訊");
}
public synchronized void call() {
System.out.println("打電話");
}
}
這次我們讓 執行緒A 呼叫物件phone的「傳簡訊」方法,而讓 執行緒B 呼叫物件phone1的「打電話」方法。
執行結果:
打電話
傳簡訊
分析:
這場景和上一個場景類似,雖然 「打電話」方法也是同步方法,但它是屬於 物件 phone1 的方法,和物件 phone 的 「傳簡訊」 方法 沒有 鎖競爭 關係。
還是那句話:
synchronized 鎖的物件是方法的呼叫者。
顯然,這次的這兩個方法不是同一個呼叫者。而是兩個,物件phone 和 物件phone1。
物件可以鎖,那麼類可不可以鎖?
可以的,看如下場景:
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone1.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsm() throws InterruptedException {
//等待3秒鐘
TimeUnit.SECONDS.sleep(3);
System.out.println("傳簡訊");
}
public static synchronized void call() {
System.out.println("打電話");
}
}
基本沒什麼變化,就是把 Phone 資源類 裡的兩個方法都加上了 static ,讓其變成了 靜態方法。
執行結果:
傳簡訊
打電話
分析:
被 static 關鍵字修飾的,會在類載入的時候被執行,他們會和類資訊一樣,被存到 方法區,是獨一無二的存在。
所以不管方法的呼叫者是不是同一個物件,他們都會存在 鎖競爭關係,因為他們都是同一個類的靜態方法。所以,這裡的鎖,鎖的是 Class。
判斷 鎖競爭關係的關鍵,就是去看兩個同步方法是不是去拿同一把鎖。
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsm() throws InterruptedException {
//等待3秒鐘
TimeUnit.SECONDS.sleep(3);
System.out.println("傳簡訊");
}
public static synchronized void call() {
System.out.println("打電話");
}
public static void hello(){
System.out.println("Hello World!");
}
}
這次我們又加上了 hello() 方法,我們把它定義成了靜態方法,同樣不是同步方法。
執行結果:
Hello World!
傳簡訊
分析:
非同步方法,根本沒有鎖競爭這一說,同一個類(Class)也沒用。
這個場景其實挺沒勁的,都老生常談的事兒了。
那我們在來看下一個場景:
import java.util.concurrent.TimeUnit;
/**
* @ClassName test1
* @Description
* @Author SkySong
* @Date 2020-10-11 18:13
*/
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒鐘
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsm() throws InterruptedException {
//等待3秒鐘
TimeUnit.SECONDS.sleep(3);
System.out.println("傳簡訊");
}
public synchronized void call() {
System.out.println("打電話");
}
}
這次我們把 執行緒B 呼叫的 call()方法 定義成了 普通同步方法,由同一個物件phone 去呼叫這兩個方法。
大家猜猜結果會是神馬?
同一個 物件phone 呼叫的,都是同步方法。結果難道是…
好了,不誤導大家了。
執行結果:
打電話
傳簡訊
分析:
還是那句話,看看他們是不是競爭同一把鎖。
首先,「傳簡訊」 是靜態方法,他應該是要去找 Class鎖,而 「打電話」 是非靜態方法,所以他找的是物件鎖。顯然,他們競爭的不是同一把鎖,所以沒有競爭關係。
換句話說,靜態同步塊鎖的是Class,而非靜態的同步塊 鎖的是呼叫者。