悲观锁/乐观锁:
悲观:操作时不让其他线程进入干预。synchronized、lock
乐观:其他线程可以干预。参考版本号机制、CAS
1.八锁现象–锁静态时/锁方法时
1.0 标准访问俩线程,先打印Email后打印SMS:
class Phone{
public synchronized void sendEmail(){
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
public void hello(){
System.out.println("-------hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
phone.sendSMS();
},"t2").start();
}
}
和中间的sleep()完全无关。
1.1:先打印邮件还是短信?:
class Phone{
public synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone.sendSMS();
},"t2").start();
}
}
结果:
-----sendEmail
-----sendSMS
1.2新增普通hello(),先打印邮件还是hello()?:
class Phone{
public synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
public void hello(){
System.out.println("-------hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone.hello();
},"t2").start();
}
}
结果:
-------hello
-----sendEmail
锁的是对象,普通无锁方法不争抢,有锁方法和无锁方法之间无冲突 ???
1.3 新增一个phone对象,先打印Email还是SMS?
class Phone{
public synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
public void hello(){
System.out.println("-------hello");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone1.sendSMS();
},"t2").start();
}
}
结果:
-----sendSMS
-----sendEmail
1.4两个静态同步方法,同一部手机,先打印E还是S?
普通同步方法锁的是new的实例对象;静态方法锁的是类了。。
class Phone{
public static synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone.sendSMS();
},"t2").start();
}
}
结果:
-----sendEmail
-----sendSMS
1.5两个phone对象两个静态同步方法,先打印邮件还是短信?
-----sendEmail
-----sendSMS
1.6:一个静态同步方法,一个普通同步方法,邮件or短信?
class Phone{
public static synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone.sendSMS();
},"t2").start();
}
}
结果:
-----sendSMS
-----sendEmail
结论:是两把不同的锁:两个线程访问的分别是类锁和对象锁
1.7:一个静态同步方法,一个普通同步方法,两个phone对象:
class Phone{
public static synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
}
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone1.sendSMS();
},"t2").start();
}
}
结果:
-----sendSMS
-----sendEmail
编程规约:
并发下,要考虑锁性能损耗,能无锁尽量无锁;能锁区块就不锁方法体;能用对象锁就不用类锁。
★结论:
0-1.一个对象中若有多个synchronized方法,某时刻内只要有一个线程调用其中的一个synchronized方法,其他线程都只能等待。
锁的是当前对象this,被锁定后,其他对象都不能进入当前线程的synchronized方法。
2-3.加普通方法后,与同步锁无关;
加了一个对象后,不出现争抢资源的情况
4-5. 都换成static方法后,情况又变化:(类锁or对象锁?)
- 普通同步方法:锁的当前实例对象,通常是tihs,具体的一部手机,所有普通同步方法用的都是同一把所----实例对象本身
- 对于静态同步方法:锁的是当前Class类对象,如Phone.class唯一一个类模板;
- 对于同步方法快,锁的是synchronized括号内的对象
6-7:普通同步方法:用的是实例对象锁
静态同步方法:用的是类锁
对象锁和类锁是俩不同的锁!!!毫不相关!!!
2.synchronized字节码
2.1正常锁住代码块
public class Lock8ByteCode {
Object object= new Object();
public void m1(){
synchronized (object){
System.out.println("-------im synchronized");
}
}
public static void main(String[] args) {
}
}
字节码:
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String -------im synchronized
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
为毛一个moniterenter对应两个monitorexit:
一个exit正常退出代码块,另一个保证出异常时退出代码块
锁住的代码块加主动加一个runtime异常:
public class Lock8ByteCode {
Object object= new Object();
public void m1(){
synchronized (object){
System.out.println("-------im synchronized");
throw new RuntimeException("-----exit");
}
}
public static void main(String[] args) {
}
}
字节码:
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String -------im synchronized
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: new #7 // class java/lang/RuntimeException
18: dup
19: ldc #8 // String -----exit
21: invokespecial #9 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
24: athrow
25: astore_2
26: aload_1
27: monitorexit
28: aload_2
29: athrow
可以看到 monitorexit被俩athrow包住,并且只有一个monitorexit
2.2 加在方法上:
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,若设置了,执行线程会先持有monitor后再执行方法,完成后(无论正常完成还是非正常完成)释放。
2.3synchronized静态同步方法:
就多了一个ACC_STATIC,其他的和2.2一样。
为何任何一个对象都能成为一个锁:
ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
每个对象都天生带着一个对象监视器。