多線程8鎖問題
在多線程環境中,訪問資源類的順序到底是如何?
最近,在網絡上看到一道題目,通過多線程的8種鎖情況瞭解訪問資源類的順序。
- 標準訪問,先打印郵件方法還是短信方法?
class Phone{
public synchronized void sendEmail(){
System.out.println("----sendEmail----");
}
public synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
//睡眠4毫秒
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone.sendSMS();
}, "B").start();
}
}
運行結果:
synchronized關鍵字鎖的不是當前方法,而是該方法所在的整個資源類,也就是說,同一時間下,只能有一個線程進入當前的資源類訪問同步方法,所以運行結果跟方法調用的順序相同。
- 郵件方法暫停4秒,先打印郵件方法還是短信方法?
class Phone {
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
運行結果:
第二鎖的原理跟第一鎖相同,只不過是在發郵件方法中線程睡眠了4秒,當線程A訪問發郵件方法睡眠時,線程B無法進入該資源類,所以運行結果還是相同。
- 新增一個普通方法hello,先打印郵件方法還是短信方法?
class Phone {
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public void hello(){
System.out.println("----hello----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
運行結果:
新增的hello方法並沒有被synchronized關鍵字修飾,不是同步方法,所以在線程A訪問發郵件方法時,線程B可以訪問到hello方法,而線程A需要睡眠4秒,所以運行結果是hello先。
- 兩個資源類,先打印郵件方法還是短信方法?
class Phone {
public synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone2.sendSMS();
}, "B").start();
}
}
運行結果:
第四鎖中創建了兩個資源類,main方法中訪問的是兩個不同資源類的方法,相互並不干擾,而發郵件的方法睡眠4秒,所以發短信的方法會先打印。
- 兩個靜態同步方法,同一個資源類,先打印郵件方法還是短信方法?
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public static synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone.sendSMS();
}, "B").start();
}
}
運行結果:
這題涉及到static靜態,鎖的不再是這個資源類對象,鎖的是這個對象在類加載器中的類模板(.class),但由於還是同一個資源類,所以的順序原理還是跟第一鎖差不多。
- 兩個靜態同步方法,兩個資源類,先打印郵件方法還是短信方法?
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public static synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone2.sendSMS();
}, "B").start();
}
}
運行結果:
第六鎖變換成兩個資源類,但由於static鎖的是整個類的類模板(.class),所以不論實例化多少個資源類訪問的結果還是相同的。
- 一個同步方法,一個靜態同步方法,一個資源類,先打印郵件方法還是短信方法?
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone.sendSMS();
}, "B").start();
}
}
運行結果:
第七鎖,一個靜態同步方法,一個普通同步方法,一個鎖的是整個類模板,一個鎖的是當前對象,兩者相互不衝突,所以發短信先顯示,發郵件的需要睡眠4秒才顯示。
- 一個同步方法,一個靜態同步方法,兩個資源類,先打印郵件方法還是短信方法?
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("----sendEmail----");
}
public synchronized void sendSMS(){
System.out.println("----sendSMS----");
}
}
public class lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "A").start();
TimeUnit.MILLISECONDS.sleep(4);
new Thread(() -> {
phone2.sendSMS();
}, "B").start();
}
}
運行結果:
第八鎖與第七鎖的原理類似。
總結
synchronized實現同步的基礎:Java中的每個對象都可以作爲鎖。
具體表現爲以下三種形式:
- 對於普通同步方法,鎖的是當前實例對象
- 對於靜態同步方法,鎖的是當前類的Class對象
- 對於同步方法快,鎖的是synchronized括號裏配置的對象