Synchronized关键字

84 阅读4分钟

synchronized

synchronized作为Java中的关键字,可以实现同步代码块。可用来给对象和方法或代码块进行加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。有两种方式对方法进行加锁操作,第一,在方法签名处加synchronized关键字;第二,使用synchronized(对象或类)进行同步。这里的原则是锁的范围尽可能小,锁的时间尽快能短,能锁对象,就不要锁类;能锁代码块,就不要锁方法。

synchronized修饰方法、代码块的区别

1111111.png

1111111.png

静态方法跟随类的生命周期,所以静态方法锁住的是对象,程序执行的之后会交替输出:

public class Data {

    public static synchronized void test1(){
        System.out.println("start.....");
        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){

        }
        System.out.println("end......");
    }   
}

public class TestSynchronized {

    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                Data.test1();
            }
        }).start();
    }
}

输出:
start.....
end......
start.....
end......
start.....
end......
start.....
end......
start.....
end......

555555.png

由于锁住的对象,即便test2()在主线程睡眠1s后执行,但是由于test1()和test2()锁定的对象不一致,所以test2()还是输出

public class Data {

    public synchronized void test1(){
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch (Exception e){

        }
        System.out.println("test1.....");
    }

    public synchronized void test2(){

        System.out.println("test2........");
    }

}

public class TestSynchronized {

    public static void main(String[] args) {
        Data data = new Data();
        Data data1 = new Data();

        new Thread(()->{
            data.test1();
        }).start();

        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (Exception e){

        }

        new Thread(()->{
            data1.test2();
        }).start();

    }
}

输出:
test2........
test1.....

444444.png

由于锁住的是字节码文件,所以每次不管线程创建了多少个对象,仍然会交替打印输出


public class Data {
    public  void test(){
        synchronized (Data.class){
            System.out.println("start...");
            try{
                TimeUnit.SECONDS.sleep(2);
            }catch (Exception e){

            }
            System.out.println("end....");
        }
    }
}

public class TestSynchronized {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                Data data = new Data();
                data.test();
            }).start();
        }
    }
}

输出:
start...
end....
start...
end....
start...
end....
start...
end....
start...
end....

333333.png

由于只new了一个对象,并且该对象加锁,所以五个线程需要等待资源释放,所以仍然是交替执行

public class Data {
    public  void test(){
        synchronized (this){
            System.out.println("start...");
            try{
                TimeUnit.SECONDS.sleep(2);
            }catch (Exception e){

            }
            System.out.println("end....");
        }
    }
}

public class TestSynchronized {

    public static void main(String[] args) {
        Data data = new Data();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                data.test();
            }).start();
        }
    }
}

输出:
start...
end....
start...
end....
start...
end....
start...
end....
start...
end....

synchronized锁特性由JVM负责实现。在JDK的不断优化迭代中,synchronized锁的性能得到极大提升,特别是偏向锁的实现,使得synchronized已经不是昔日那个低性能且笨重的锁了。JVM底层通过监视锁来实现synchornized同步的。监视锁如monitor,是每个对象与生俱来的一个隐藏字段。使用synchronized时,JVM会根据synchronized的当前使用环境,找到对应对象得monitor,再根据monitor的状态进行加、解锁的判断。例如,线程在进入同步方法或代码块时,会根据该方法或代码块所属对象的monitor,进行加锁判断。如果成功枷锁就成为该monitor的唯一持有者。monitor在被释放前,不能再被其他线程获取。下面通过 javap -C xxxx.java 命令查看反编译的文件:


    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void test();
    Code:
       0: ldc           #2                 
       2: dup
       3: astore_1
       4: monitorenter
       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: ldc           #4                  // String start...
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: getstatic     #6                  // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
      16: ldc2_w        #7                  // long 2l
      19: invokevirtual #9                  // Method java/util/concurrent/TimeUnit.sleep:(J)V
      22: goto          26
      25: astore_2
      26: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      29: ldc           #11                 // String end....
      31: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      34: aload_1
      35: monitorexit
      36: goto          44
      39: astore_3
      40: aload_1
      41: monitorexit
      42: aload_3
      43: athrow
      44: return
    Exception table:
       from    to  target type
          13    22    25   Class java/lang/Exception
           5    36    39   any
          39    42    39   any
}

方法元信息中会使用ACC_SYNCHRONIZED标识该方是一个同步方法。同步代码块中会使用monitorenter及monitorexit两个字节码指令获取和释放monitor。如果使用monitorenter进入时monitor为0,表示该线程可以持有monitor后续代码,并将monitor加1;如果当前线程已经有monitor,那么继续加1,如果monitor非0,其他线程就会进入阻塞状态。JVM对synchronized的优化主要在于对monitor的加锁,解锁上。JDK6后不断优化使得synchronized提供三种锁的实现,包括偏向锁、轻量级锁、重量级锁,还提供自动升级和降级机制。JVM就是利用CAS在对象头上设置线程ID,表示这个对象偏向于当前线程,这就是偏向锁。

总体流程:

666666.png

synchronized和volatile的区别

volatile解决的是多线程共享变量的可见性问题,但不能保证原子性。 如果是一写多读的并发场景,使用volatile修饰变量则非常合适。

synchronized能保证可见性、原子性、有序性,但会造成线程阻塞

synchronized和Lock的区别

synchronized是关键字,属于JVM层面,自动释放锁,不可以中断

Lock是接口,需要手动释放,可以中断


    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
    void unlock();
    Condition newCondition();

synchronized产生死锁例子


public static void main(String[] args) {

    Object a  = new Object();
    Object b = new Object();

    new Thread(()->{
        synchronized (a){
            System.out.println(Thread.currentThread().getName() + " a........");
            try{
                TimeUnit.SECONDS.sleep(2);
            }catch (Exception e){

            }
            synchronized (b){
                System.out.println(Thread.currentThread().getName() + " b........");
            }
        }
    },"t1").start();


    new Thread(()->{
        synchronized (b){
            System.out.println(Thread.currentThread().getName() + " b........");
            try{
                TimeUnit.SECONDS.sleep(2);
            }catch (Exception e){

            }
            synchronized (a){
                System.out.println(Thread.currentThread().getName() + " a........");
            }
        }
    },"t2").start();

}