我们知道,使用synchronized关键字修饰方法时,多个线程调用此方法,会互斥执行。如果synchronized修饰不同的方法,多个线程再分别调用这些方法时是互斥的吗?下面使用代码模拟一下:
新建一个User类:
public class User {
public synchronized void getUp() {
System.out.println("起床...");
try {
TimeUnit.SECONDS.sleep(2L); // 暂停2s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void eatBreakfast() {
System.out.println("吃早餐...");
}
}
User类中,两个方法都使用synchronized关键字修饰了,写一个测试方法测试一下:
注意:Junit单元测试时,当主线程结束后,不管子线程是否结束都会退出,所以这里使用CountDownLatch,让主线阻塞等待子线程运行完成。
@Test
public void test1() {
CountDownLatch latch = new CountDownLatch(2);
User user = new User(); // 创建user对象
User user2 = new User(); // 创建user2对象
new Thread(() -> {
user.getUp();
latch.countDown();
}).start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
user2.eatBreakfast();
latch.countDown();
}).start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
起床...
吃早餐...
可以看到,创建两个对象调用不同的synchronized修饰的方法的时候,并没有出现互斥访问的情况,eatBreakfast方法并没有等待getUp方法执行完毕再执行,原因也很简单,这两个方法获取的不是同一个锁对象。
修改测试方法,改成一个对象来访问不同的synchronized修饰的方法:
@Test
public void test2() {
CountDownLatch latch = new CountDownLatch(2);
User user = new User(); // 创建user对象
new Thread(() -> {
user.getUp();
latch.countDown();
}).start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
user.eatBreakfast();
latch.countDown();
}).start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
起床...
吃早餐...
可以看到,方法是顺序执行的,也就是互斥访问的,getUp方法中,使用TimeUnit.SECONDS.sleep(2L)让方法延迟了2s,然后在测试方法中延迟1s调用eatBreakfast方法,如果方法不是互斥访问的,应该是eatBreakfast先执行,为了看清效果,你可以让getUp方法延迟更久,看一下是不是还是要等getUp方法执行完毕再执行eatBreakfast方法。
这说明,synchronized锁的对象是调用者,这两个方法用的是同一把锁,谁先拿到,谁先执行,执行完毕释放锁以后,下一个方法获取锁才执行,而不是谁先被调用就一定先被执行。同理static synchronized修饰的静态同步方法也是类似的,由于锁是字节码文件对象,调用是也是互斥的,谁先获取锁谁就先被执行。