当问到多线程时候如何解决线程安全的问题时候,大部分人都知道加锁。提到锁最先接触到的就是Synchronized关键字。
当我们想要保证一个共享资源在同一时间只会被一个线程访问到时,我们可以在代码中使用synchronized关键字对类或者对象加锁
synchronized常用的有以下三种使用方式:
▪修饰代码块,即同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。
▪ 修饰普通方法,即同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
▪ 修饰静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
/*** 对象锁*/public class Test{// 对象锁:形式1(方法锁)public synchronized void method1(){System.out.println("对象锁也是方法锁");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}// 对象锁:形式2(代码块形式)public void method2(){synchronized (this){System.out.println("对象锁2");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}}}/*** 方法锁(即对象锁中的形式1)*/public synchronized void method1(){System.out.println("对象锁也是方法锁");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}/*** 类锁*/
public class Test{// 类锁:形式1 :锁静态方法public static synchronized void method1(){System.out.println("类锁1");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}// 类锁:形式2 :锁静态代码块public void method2(){synchronized (Test.class){System.out.println("类锁2");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}}
}
使用javap -c Test可以查看编译之后的具体信息
/修饰方法
public synchronized void method1();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #3 // String 对象锁也是方法锁5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return
//类锁public void method2();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: ldc #5 // class com/demo/Test2: dup3: astore_14: monitorenter5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc #3 // String 类锁210: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto 2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: return
可以看到在同步块的入口和出口分别有monitorenter和monitorexit指令。当执行monitorenter指令时,线程试图获取锁也就是获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因)的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
在synchronized修饰方法时是添加ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
要想真正了解Monitor怎么实现加锁和解锁的,需要先了解对象头。让我们看看对象模型。
HotSpot虚拟机中,设计了一个OOP-Klass Model。OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象实例的具体类型。