Phone 有兩個方法:傳送郵件和傳送簡訊,每個方法都列印一句話,現在通過不同的方式對方法進行操作,回答出列印的先後順序(建議先自己看程式碼認真思考,然後再看答案,文章結尾會對每個問題進行分析)
問題
1、標準訪問,兩執行緒中間睡眠 2 毫秒,先列印郵件還是簡訊?
class Phone {
public synchronized void sendEmail() {
System.out.println("send email");
}
public synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock01 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> phone.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone.sendSms(), "B").start();
}
}
檢視答案
send emailsend sms
2、在 sendEmail() 方法中睡眠 4 秒,先列印郵件還是簡訊?
class Phone {
public synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock02 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> phone.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone.sendSms(), "B").start();
}
}
檢視答案
send emailsend sms
3、新增普通的 hello() 方法,先列印郵件還是 hello?
class Phone {
public synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public void hello() {
System.out.println("hello");
}
}
public class Lock03 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> phone.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone.hello(), "B").start();
}
}
檢視答案
hellosend email
4、2 個手機,先列印郵件還是簡訊?
class Phone {
public synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock04 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> phone1.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone2.sendSms(), "B").start();
}
}
檢視答案
send smssend email
5、2個靜態同步方法,1部手機,先列印郵件還是簡訊?
class Phone {
public static synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public static synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock05 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> phone.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone.sendSms(), "B").start();
}
}
檢視答案
send emailsend sms
class Phone {
public static synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public static synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock06 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> phone1.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone2.sendSms(), "B").start();
}
}
檢視答案
send emailsend sms
class Phone {
public static synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock07 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> phone.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone.sendSms(), "B").start();
}
}
檢視答案
send smssend email
8、1個靜態同步方法,1個普通同步方法,2部手機,先列印郵件還是簡訊?
class Phone {
public static synchronized void sendEmail() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("send email");
}
public synchronized void sendSms() {
System.out.println("send sms");
}
}
public class Lock08 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> phone1.sendEmail(), "A").start();
Thread.sleep(200);
new Thread(() -> phone2.sendSms(), "B").start();
}
}
檢視答案
send smssend email
分析
? 問題一
當一個物件裡有多個同步(synchronized)方法,有一個執行緒訪問了其中一個同步方法,其它執行緒只能等待其訪問完成後才能訪問,因為此時鎖的是當前物件 this,其它的執行緒都不能進入到當前物件的其它的同步方法。
如果沒有新增 Thread.sleep(200);
則列印的順序是不一定的,因為執行緒的排程和作業系統有關。 新增 Thread.sleep(200);
則保證了執行緒 A 比 B 先執行。
? 問題二
由於執行緒 A 先執行,會先呼叫 sendEmail() 方法,Phone 例項就會被鎖住,執行緒 B 只能等待 A 執行完在執行。
? 問題三
hello() 方法並不是同步方法,因此不受鎖的影響。
? 問題四
現在有兩個例項,前面我們說過,synchronized 鎖的是 this,所以會產生兩把鎖,它們之間互不干擾,誰先執行完誰就先列印。
? 問題五、問題六
synchronized 實現同步的基礎:Java 中的每一個物件都可以作為鎖,具體表現為以下三種形式:
- 對於普通同步方法,鎖的是當前例項物件 this
- 對於靜態同步方法,鎖的是當前類的 Class 物件
- 對於同步方法塊,鎖是Synchonized括號裡配置的物件
所以,無論是 1 個物件還是 2 個物件,靜態同步方法鎖的都是 Class,只能按照執行緒執行的順序列印。
? 問題七、問題八
這兩種情況都是 1 個靜態同步方法,1 個非靜態同步方法,它們的鎖都不是同一個物件,因此相互不受影響
總結
1、當一個執行緒試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常時必須釋放鎖。
2、Java 中的每一個物件都可以作為鎖;普通同步方法鎖 this,靜態同步方法鎖 Class,同步方法塊鎖括號;
3、只要鎖的物件不是同一個,就直接按照執行緒執行的快慢來決定;鎖的物件是同一個,就按照執行緒進入的先後順序決定。
只要掌握了鎖的物件是什麼,無論是 8 鎖還是 100 鎖都不在話下!