synchronized 的 8 种使用案例分析
你知道 synchronized 是怎么用的吗?😮
现在有一个 HuaWei
类,它有三个方法 sendMail
,sendSMS
,hello
class HuaWei {
public synchronized void sendMail() {
System.out.println(Thread.currentThread().getName() + ": send Mail");
}
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + ": send SMS");
}
public void hello() {
System.out.println(Thread.currentThread().getName() + ": say hello");
}
}
我们会在以下 8 种案例下对方法进行访问并分析原因:
- 标准访问有 A,B 两个线程,请问先打印邮件还是短信?
- sendEmail 方法中加入暂停 3 秒钟,请问先打印邮件还是短信?
- 添加一个普通的 hello 方法,请问先打印邮件还是 hello?
- 有 2 部手机,请问先打印邮件还是短信?
- 有两个静态同步方法,有 1 部手机,请问先打印邮件还是短信?
- 有两个静态同步方法,有 2 部手机,请问先打印邮件还是短信?
- 有 1 个静态同步方法,有 1 个普通同步方法,有 1 部手机,请问先打印邮件还是短信?
- 有 1 个静态同步方法,有 1 个普通同步方法,有 2 部手机,请问先打印邮件还是短信?
标准访问有 A,B 两个线程,请问先打印邮件还是短信?
public class MyTest {
public static void main(String[] args) throws InterruptedException {
HuaWei huaWei = new HuaWei();
new Thread(() -> {
huaWei.sendMail();
}, "线程A").start();
Thread.sleep(200); // 保证线程 A 先执行
new Thread(() -> {
huaWei.sendSMS();
}, "线程B").start();
}
}
结果如下:
线程A: send Mail
线程B: send SMS
结果分析:
加在普通方法上的 synchronized
使用当前对象作为监视器,是对象锁,同一时刻仅允许单个线程访问同一个对象实例。这里线程 A 先启动抢占锁,线程 B 后启动等待。虽然访问方法不同,但是是同一对象,必须等待锁释放后 B 才能访问该对象实例,故 A 先一步完成并打印结果。
sendEmail 方法中加入暂停 3 秒钟,请问先打印邮件还是短信?
class HuaWei {
public synchronized void sendMail() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": send Mail");
}
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + ": send SMS");
}
public void hello() {
System.out.println(Thread.currentThread().getName() + ": say hello");
}
}
结果如下:
线程A: send Mail
线程B: send SMS
结果分析:
虽然 A 睡了 1 秒,但 Thread.sleep()
方法不会使线程释放锁,因此 B 依旧无法拿到锁被迫等待,A 醒来后执行完毕并释放锁,B 拿到锁后才能执行。
添加一个普通的 hello 方法,请问先打印邮件还是 hello?
HuaWei
类不变,MyTest
如下:
public class MyTest {
public static void main(String[] args) throws InterruptedException {
HuaWei huaWei = new HuaWei();
new Thread(() -> {
huaWei.sendMail();
}, "线程A").start();
Thread.sleep(200);
new Thread(() -> {
huaWei.hello(); // 调用 hello()
}, "线程B").start();
}
}
结果如下:
线程B: say hello
线程A: send Mail
结果分析:
加在普通方法上的 synchronized
对象锁,仅会锁住对象实例上的所有同步方法,与普通方法不产生互斥。这里 A 先启动抢占锁后睡眠,B 后启动访问普通方法 hello()
不会产生互斥,无需等待锁释放即可执行完毕,因此 B 先一步打印结果。
有 2 部手机,请问先打印邮件还是短信?
HuaWei
类不变,MyTest
如下:
public class MyTest {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new HuaWei().sendMail();
}, "线程A").start();
Thread.sleep(200);
new Thread(() -> {
new HuaWei().sendSMS();
}, "线程B").start();
}
结果如下:
线程B: say hello
线程A: send Mail
结果分析:
对象锁只锁同一对象实例,不同对象之间的锁不相同。B 无需等待 A 的锁释放就能拿到自己对象的锁并执行方法,两者拿到的是不同的锁。又因为 A 要睡 1 秒,所以 B 先打印结果。
有两个静态同步方法,有 1 部手机,请问先打印邮件还是短信?
package fun.hualiang.test;
class HuaWei {
public static synchronized void sendMail() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": send Mail");
}
public static synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + ": send SMS");
}
public void hello() {
System.out.println(Thread.currentThread().getName() + ": say hello");
}
}
public class MyTest {
public static void main(String[] args) throws InterruptedException {
HuaWei huaWei = new HuaWei();
new Thread(() -> {
huaWei.sendMail();
}, "线程A").start();
Thread.sleep(200);
new Thread(() -> {
huaWei.sendSMS();
}, "线程B").start();
}
}
结果如下:
线程A: send Mail
线程B: send SMS
结果分析:
加在静态方法上的 synchronized
使用当前类作为监视器,是类锁,同一时刻仅允许单个线程访问同一个类。这里的原因跟情况二类似,区别在于情况二拿到的是对象锁,这里拿到的是类锁。
有两个静态同步方法,有 2 部手机,请问先打印邮件还是短信?
HuaWei
类不变,MyTest
如下:
public class MyTest {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new HuaWei().sendMail();
}, "线程A").start();
Thread.sleep(200);
new Thread(() -> {
new HuaWei().sendSMS();
}, "线程B").start();
}
}
结果如下:
线程A: send Mail
线程B: send SMS
结果分析:
这种情况与情况四的区别在于,情况四中 A 和 B 分别拿到的是不同对象上的对象锁,不会产生互斥。而这里因为静态方法是属于类的,所以 A 和 B 拿到的是同一个类的类锁,既然是同一把锁就会产生互斥。A 先启动拿到锁,B 就只能等 A 执行完释放锁后在执行,故 A 先一步打印结果。
有 1 个静态同步方法,有 1 个普通同步方法,有 1 部手机,请问先打印邮件还是短信?
package fun.hualiang.test;
class HuaWei {
public static synchronized void sendMail() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": send Mail");
}
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + ": send SMS");
}
public void hello() {
System.out.println(Thread.currentThread().getName() + ": say hello");
}
}
public class MyTest {
public static void main(String[] args) throws InterruptedException {
HuaWei huaWei = new HuaWei();
new Thread(() -> {
huaWei.sendMail();
}, "线程A").start();
Thread.sleep(200);
new Thread(() -> {
huaWei.sendSMS();
}, "线程B").start();
}
}
结果如下:
线程B: send SMS
线程A: send Mail
结果分析:
B 拿的是对象锁,A 拿的是类锁,不同的锁不会发生互斥,因此结果与情况四类似,都是拿到不同的锁。
有 1 个静态同步方法,有 1 个普通同步方法,有 2 部手机,请问先打印邮件还是短信?
HuaWei
类不变,MyTest
如下:
public class MyTest {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
new HuaWei().sendMail();
}, "线程A").start();
Thread.sleep(200);
new Thread(() -> {
new HuaWei().sendSMS();
}, "线程B").start();
}
}
结果如下:
线程B: send SMS
线程A: send Mail
结果分析:
与情况七一致,即使是不同对象,也不影响 B 拿的是对象锁,A 拿的是类锁,因此结果一致。
总结
综上所述,多线程是否同步的关键在于:它们是否拿到的是同一把锁。