本文主要介绍常见面试题,JUC并发编程中的八锁现象。通过这篇文章,可以让你彻底搞清楚锁对象或者是锁方法的不同之处!
问题一:标准情况下,一个对象,两个同步方法先执行哪一个?
1、测试代码:
public class Test01 {
public static void main(String[] args) {
//创建资源对象
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
//资源类
class Phone {
public synchronized void sendSms() {
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
控制台打印结果:
先打印发短信,等待一秒后打印打电话
2、总结
Synchronized锁,锁的是对象的调用者,因为这两个线程同时用的一个对象phone,那么谁先拿到这把锁,谁就执行对应的方法
问题二:给其中一个同步方法加一个2秒的延时,先执行哪一个方法?
1、测试代码
public class Test01 {
public static void main(String[] args) {
//创建资源对象
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
//模拟延时
/* try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(() -> {
phone.call();
}, "B").start();
}
}
//资源类
class Phone {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
控制台打印结果:
延迟2秒后,打印顺序为发短信 打电话
2、总结
Synchronized锁,锁的是对象的调用者,因为这两个线程同时用的一个对象phone,那么谁先拿到这把锁,谁就执行对应的方法
问题三:资源类中增加普通方法,先执行加锁的还是普通的方法?
1、测试代码
public class Test02 {
public static void main(String[] args) {
//创建资源对象
Phone2 phone = new Phone2();
new Thread(() -> {
phone.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
//资源类
class Phone2 {
public synchronized void sendSms() {
//模拟延时
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void hello(){
System.out.println("Hello");
}
}
控制台打印结果:
先打印hello,过了4秒钟打印发短信
2、总结
普通方法没有加Synchronized锁,那么他就不是同步方法,不受锁的影响
问题四:创建两个对象,都执行同步方法,先执行哪一个?
1、测试代码
public class Test02 {
public static void main(String[] args) {
//创建资源对象,创建两个对象
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
//资源类
class Phone2 {
public synchronized void sendSms() {
//模拟延时
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void hello(){
System.out.println("Hello");
}
}
控制台打印结果:
先打印打电话,过了4秒钟打印发短信
2、总结
Synchronized锁的是对象的调用者,因为我们创建了两个对象,那么就会有两把锁,锁不一样,就按照他们各自的延迟时间执行,互不影响
问题五:增加两个静态的同步方法,两个线程调用,先执行哪一个?
1、测试代码
public class Test03 {
public static void main(String[] args) {
//创建资源对象
Phone3 phone = new Phone3();
new Thread(() -> {
phone.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
//资源类
class Phone3 {
public static synchronized void sendSms() {
//模拟延时
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
控制台打印结果:
等待4秒钟打印发短信,紧接着执行打电话
2、总结
和问题一对比学习!
其一是Synchronized锁的对象是方法的调用者,因为只创建了一个对象,那么谁先拿到锁,谁先执行
其二是因为加了Static关键字,该方法就变成了静态方法,随着类的加载而加载,那么这个测试中的锁锁的是Class这个模板,Class模板全局唯一,也就是说他们还是共用一把锁,谁先拿到锁,谁先执行方法
问题六:创建两个对象,调用两个静态同步方法,先执行哪一个?
1、测试代码
public class Test03 {
public static void main(String[] args) {
//创建资源对象
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
//资源类
class Phone3 {
public static synchronized void sendSms() {
//模拟延时
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
控制台打印结果:
和问题四执行结果相同,等待4秒钟打印发短信,紧接着执行打电话
2、总结
出现和问题四相同的执行结果的原因是Synchronized锁的是两个静态的方法,静态方法随着类的加载而加载,尽管我们创建了两个对象,但是他们的Class模板全局唯一,Synchronized锁锁的是这个唯一的Class模板,因此他们还是共用一把锁,所以还是先执行先得到锁的发短信方法,尽管他有4秒的延时。
问题七:一个静态同步方法,一个普通同步方法,一个对象,先执行哪个方法呢?
1、测试代码
public class Test04 {
public static void main(String[] args) {
//创建资源对象
Phone4 phone = new Phone4();
new Thread(() -> {
phone.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
//资源类
class Phone4 {
//静态同步方法
public static synchronized void sendSms() {
//模拟延时
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法
public synchronized void call() {
System.out.println("打电话");
}
}
控制台打印结果:
先打印打电话,等待4秒后执行发短信
2、总结
静态同步方法锁的是Class模板,而普通同步方法锁的是对象的调用者,本质上是两把锁,执行的时候互不影响,按照他们本身的延迟时间进行先后的执行
问题八:两个对象,一个静态同步方法,一个普通同步方法,先执行哪一个?
1、测试代码
public class Test04 {
public static void main(String[] args) {
//创建资源对象
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
//模拟延时
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
//资源类
class Phone4 {
//静态同步方法
public static synchronized void sendSms() {
//模拟延时
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法
public synchronized void call() {
System.out.println("打电话");
}
}
控制台打印结果:
先打印打电话,等待4秒后执行发短信方法
2、总结
还是同样的道理,静态同步方法锁的是Class模板,而普通同步方法锁的是对象的调用者,本质上还是两把锁,尽管我们创建了两个对象,但是依旧不改变两把锁的根本,所以执行结果是先打印打电话,等待4秒后执行发短信方法。
总结
主要就是搞清楚锁的对象是什么,new出来的是具体的对象,可以不唯一,但是通过static修饰的同步方法,是锁的全局唯一的Class模板