阿里巴巴规范手册中有提到:高并发时,同步调用应该去考量锁的性能消耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
“八锁”案例
案例一
有一个Phone资源类,该资源类包含一个被synchronized修饰的发邮件方法和另一个synchronized修饰的发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public synchronized void sendEmail() {
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public synchronized void sendSMS() {
System.out.println("sendSMS success");
}
}
只有有一部手机,A线程发邮件,B线程发短信。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.sendSMS();
}, "B").start();
}
打印结果:
sendEmail success
sendSMS success
结论很简单,A线程和B线程争抢的是同一个锁对象,这里的锁对象是phone1对象锁(Object Lock)。所以会先发送邮件,然后在发送短信。
案例二
有一个Phone资源类,该资源类包含一个被synchronized修饰的发邮件方法,发送邮件的方法延时3秒和另一个synchronized修饰的发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public synchronized void sendEmail() {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public synchronized void sendSMS() {
System.out.println("sendSMS success");
}
}
只有有一部手机,A线程发邮件,B线程发短信。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.sendSMS();
}, "B").start();
}
打印结果:
sendEmail success
sendSMS success
结论很简单,A线程和B线程争抢的是同一个锁对象,这里的锁对象是phone1对象锁(Object Lock)。即使发邮件方法延时3秒钟,但是A线程依然持有phone1对象锁。所以会先发送邮件,然后在发送短信。
案例三
有一个Phone资源类,该资源类包含一个被synchronized修饰的发邮件方法,发送邮件的方法延时3秒和另一个普通的发送短信的方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public synchronized void sendEmail() {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public void sendSMS() {
System.out.println("sendSMS success");
}
}
只有一部手机,A线程调用发邮件方法,B线程调用发短信方法。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.sendSMS();
}, "B").start();
}
打印结果:
sendSMS success
sendEmail success
可以看到,先发送了短信,再发送了邮件。因为发送短信方法并没有加锁,所以B线程在调用发送短信方法时并不存在锁的抢占。
案例四
有一个Phone资源类,该资源类包含一个被synchronized修饰的发邮件方法和另一个synchronized修饰的发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public synchronized void sendEmail() {
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public synchronized void sendSMS() {
System.out.println("sendSMS success");
}
}
有两部手机,A线程调用第一部手机的发邮件方法,B线程调用第二部手机的发短信方法。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.sendSMS();
}, "B").start();
}
打印结果:
sendSMS success
sendEmail success
这里的结论也很简单,因为这里A线程和B线程所持有的锁分别是phone1和phone2的对象锁,两个线程之间并不存在锁的抢占。所以发短信会在发邮件之前。
案例五
有一个Phone资源类,该资源类包含一个被synchronized修饰的静态的发邮件方法和另一个synchronized修饰的静态的发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public static synchronized void sendEmail() {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public static synchronized void sendSMS() {
System.out.println("sendSMS success");
}
}
只有一部手机,A线程调用发邮件方法,B线程调用发短信方法。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.sendSMS();
}, "B").start();
}
打印结果:
sendEmail success
sendSMS success
这里的结果和案例一的结果一样,先发送了邮件再发送了短信。因为,此时A线程和B线程所抢占的还是同一把锁,只不过这个时候锁对象变成了Phone的类锁(Class Lock)。所以,先发送了邮件,再发送了短信。
案例六
有一个Phone资源类,该资源类包含一个被synchronized修饰的静态的发邮件方法和另一个synchronized修饰的静态的发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public static synchronized void sendEmail() {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public static synchronized void sendSMS() {
System.out.println("sendSMS success");
}
}
两部手机,A线程调用第一部手机的发邮件方法,B线程调用第二部手机的发短信方法。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.sendSMS();
}, "B").start();
}
结果打印:
sendEmail success
sendSMS success
这里的结果和案例六的结果一样,虽然是两部手机,但是A线程和B线程抢占的还是同一个锁对象,就是Phone的类锁(Class Lock)。所以,先发送了邮件,再发送了短信。
案例七
有一个Phone资源类,该资源类包含一个被synchronized修饰的静态的发邮件方法和另一个普通发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public void sendSMS() {
System.out.println("sendSMS success");
}
}
一部手机,A线程调用发邮件方法,B线程调用发短信方法。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.sendSMS();
}, "B").start();
}
结果打印:
sendSMS success
sendEmail success
经过前面几个案例之后,这里的结果就显得比价简单了。应为A线程和B线程不存在锁的抢占。所以就B线程先发送了短信,A线程再发送邮件。
案例八
有一个Phone资源类,该资源类包含一个被synchronized修饰的静态的发邮件方法和另一个普通发短信方法:
/**
* 资源类
*/
class Phone {
/**
* 发邮件
*/
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendEmail success");
}
/**
* 发短信
*/
public void sendSMS() {
System.out.println("sendSMS success");
}
}
两部手机,A线程调用第一步手机发送邮件,B线程调用第二部手机的发送短信方法。先发送邮件还是先发送短信?
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendEmail();
}, "A").start();
//保证A线程早于B线程启动
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.sendSMS();
}, "B").start();
}
打印结果:
sendSMS success
sendEmail success
该案例中的A线程持有的是Phone类锁(Class Lock),而B线程并不存在和A线程抢占锁的关系,所以先发送了短息,再发送了邮件。
通过上面几个案例,同学们对类锁(Class Lock)和对像锁(Object Lock)有一定的认识了吧。