小周学JAVA—八股三

当问到多线程时候如何解决线程安全的问题时候,大部分人都知道加锁。提到锁最先接触到的就是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: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: 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;)V
         8: return
//类锁
  public void method2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #5                  // class com/demo/Test
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #3                  // String 类锁2
        10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: 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用来描述对象实例的具体类型。

你可能感兴趣的:(java,开发语言)