Java并发编程-synchronized关键字

synchronized关键字

Java并发编程时,遇到多线程抢占共享资源的问题,都知道要给共享资源加锁。Java目前提供的加锁的方式主要是两种:synchronized的阻塞方式和基于AQS(AbstractQueuedSynchronizer)的相关实现(ReentrantLock等靠维护队列实现)。本文介绍synchronized的实现及原理。
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

synchronized关键字用法

总体来说synchronized关键字有两种用法,用在方法上和用在代码块,但是方法又又静态和非静态方法,所有也可以说有三种用法。
synchronized关键字最主要的三种使用方式:
1.修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
2.修饰静态方法: 也就是给当前类加锁,其实是用类对象加锁,会作用于类的所有对象实例 ,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
3.修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

看下面代码:

public class TestDemo {
	//synchronized 代码块
    public void synchronizedTest(){
        synchronized (this){
            System.out.println("test synchronized code");
        }
    }
	//synchronized 方法
    public synchronized void synchronizedTest2(){
        System.out.println("test synchronized method");
    }
}

上面可以看到这就是synchronized两种使用方式:同步代码块和同步方法,静态方法时只是锁定的对象是class对象其他与非静态方法一致。上面代码进行javap命令反汇编后看的更加清晰:

javap -c -s -v -l TestDemo.class

public com.zenmesir.TestDemo();

  public void synchronizedTest();
    Signature: ()V
    flags: ACC_PUBLIC
    
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0       
         1: dup           
         2: astore_1      
         3: monitorenter  
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String test synchronized code
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1       
        13: monitorexit   
        14: goto          22
        17: astore_2      
        18: aload_1       
        19: monitorexit   
        20: aload_2       
        21: athrow        
        22: return        
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
     
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/zenmesir/TestDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
           frame_type = 250 /* chop */
          offset_delta = 4


  public synchronized void synchronizedTest2();
    Signature: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED

    LineNumberTable:
      line 12: 0
      line 13: 8
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String test synchronized method
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return        

先看同步代码块,同步代码块的同步实现方式是通过monitorenter和monitorexit指令实现的。(未完待续)

你可能感兴趣的:(并发编程,java,并发编程)