java之Synchronized

1、synchronized的作用

为什么需要synchronized?在我们并发编程中,处理公共数据时,需要考虑多个线程同时处理导致的问题,这时候需要用到synchronized来修饰方法,保证其原子性。

2、作用域

(1)方法

(2)代码块

(3)静态方法

2.1方法

下面修饰方法后,锁的是当前实例对象,如果是不同的实例对象调用此方法是不生效的。

    private int i=0;
    public synchronized void add() {
        System.out.println("---- start");
        i++;
        System.out.println("---- end");
    }

2.2 代码块

锁的对象是this,作用范围是当前实例对象。

    private int i=0;
    public void add() {
        synchronized(this) {
            System.out.println("---- start");
            i++;
            System.out.println("---- end");
        }
    }

锁的对象是object,作用范围是当前实例对象。 

    private Object object = new Object();
    public void add1() {
        synchronized(object) {
            System.out.println("---- start");
            i++;
            System.out.println("---- end");
        }
    }

锁对象是class类,作用范围所有对象公用一把锁 

    public void add1() {
        synchronized(SynTest.class) {
            System.out.println("---- start");
            i++;
            System.out.println("---- end");
        }
    }

锁对象是class对象,作用范围所有对象公用一把锁  

    private static Object staticObject = new Object();
    public void add3() {
        synchronized(staticObject) {
            System.out.println("---- start");
            i++;
            System.out.println("---- end");
        }
    }

 2.3 静态方法

在静态方法上面加锁,作用范围所有对象公用一把锁  

    private static int k=0;
    public static synchronized void add4() {
        System.out.println("---- start");
        k++;
        System.out.println("---- end");
    }

 3、synchronized原理

下面我们看一下同步代码的源码。对以下java代码编译成class文件后,使用javap反编译查看其字节码命令。

   Object object = new Object();

   public void method1() {
       synchronized (object) {

       }
   }

我们通过一下字节码命令中可以看到monitorenter和monitorexit指令

  public void method1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field object:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: aload_1
         8: monitorexit
         9: goto          17
        12: astore_2
        13: aload_1
        14: monitorexit
        15: aload_2
          locals = [ class org/example/SynTest, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}

monitorenter和monitorexit指令,会让对象执行时,使其锁计数器+1或者-1,每个对象同一时间只和一个monitor关联。一个monitor同一时间只能被一个线程获得,当一个对象在尝试获取和这个对象关联的monitor锁的所有权的时候,monitorenter会发生下面3中情况:

1、当monitor计数器为0时,说明没有线程占用此锁,当一个线程获取到锁后,此计数器则会加1

2、当此线程已经获取到monitor的所有权时,若再次重入锁,则计数器会在加1,变成2,随着重入的次数,计数器会一直累加

3、当此线程拥有了monitor锁之后,其他线程只能等待锁释放

monitorexit指令:当同步方法执行完之后,会进入monitorexit,计数器则会减1,如是从重入方法中退出,随着退出的次数,计数器会持续减1,直到减为0,说明释放锁。

可重入锁

可重入的含义是,下图中当调用method1方法后,使用同一把锁可以进入method2和method3。既可以使用同一把锁进入自己的子程序,即可重入。

   private synchronized void method1() {
        System.out.println(Thread.currentThread().getId() + ": method1()");
        method2();
    }

    private synchronized void method2() {
        System.out.println(Thread.currentThread().getId()+ ": method2()");
        method3();
    }

    private synchronized void method3() {
        System.out.println(Thread.currentThread().getId()+ ": method3()");
    }

执行monitorenter获取锁

刚开始计数器为0

执行method1()  计数器+1 ->1

执行method2()  计数器+1 ->2

执行method3()  计数器+1 ->3

执行monitorexit释放锁

执行完method3()  计数器-1 ->2

执行完method2()  计数器-1 ->1

执行完method1()  计数器-1 ->0

释放锁

 4、synchronized和lock区别

(1)synchronized 没有lock这么多方法,不够灵活,没有获取到锁,则一直等待,没有尝试获取的时间方法

(2)synchronized不能多个条件锁,而lock和condition可以实现多条件锁

(3)没有lock的tryLock方法,如果获取锁则会。。

(4)synchronized内部使用的monitor实现的,而lock底层使用AQS,而AQS底层使用的park和unpark

(5)synchronized不需要手动释放锁

(6)synchronized是可重入,不可中断,非公平锁,lock是可重入、可中断、公平和非公平都支持的锁

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