关于java的Synchronized,你可能须要知道这些(上)

  对于使用java同窗,synchronized是再熟悉不过了。synchronized是实现线程同步的基本手段,然而底层实现仍是经过锁机制来保证,对于被synchronized修饰的区域每次只有一个线程能够访问,从而知足线程安全的目的。那么今天就让咱们聊一聊synchronized的那些事
1.基本用法
   java内存模型(JMM)围绕原子性,可见性、有序性以及Happen-before原则展开(参考http://www.cnblogs.com/hujunzheng/p/5118256.html), synchronized经过锁机制的实现,知足了原子性,可见性和有序性,是并发编程正确执行的有效保障,而volatile只保证了可见性和有序性(禁止指令重排)。
  synchronized能够修饰范围的包括:方法级别,代码块级别;而实际加锁的目标包括:对象锁(普通变量,静态变量),类锁。
  下面是synchronized的几种经常使用方法:
public class SynMethod {
    private static final Object staticLockObj = new Object();
    /**
     * 对象锁,代码级别,同一对象争用该锁,this为SynMethod实例,synchronized的锁绑定在this对象上
     */
    public void method1() {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }
    
    /**
     * 对象锁,方法级别,同一对象争用该锁,普通(非静态)方法,synchronized的锁绑定在调用该方法的对象上,与上一个写法含义一致
     */
    public synchronized void method2() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
        }
    }

    /**
     * 对象锁,代码级别,同一类争用该锁,绑定在staticLockObj上,不一样SynMethod实例,拥有同一个staticLockObj对象
     */
    public void method3() {
        synchronized (staticLockObj) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }

    /**
     * 类锁,代码级别,同一类争用该锁
     */
    public void method4() {
        synchronized (SynMethod.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }

    /**
     * 类锁,方法级别,同一类争用该锁,synchronized的锁绑定在SynMethod.class上
     */
    public static synchronized void staticMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
        }
    }
}

  下面咱们来测试一下(因为同步运行结果收到线程调度等各类影响,对于没法达到同步效果的状况,须要多运行几回)html

   测试状况1java

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method1();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method1();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:编程

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程运行了同一个对象t1的同一个public方法method1,这个方法在t1对象上同步,因此实现了同步的效果缓存

  测试状况2安全

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method1();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method2();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:并发

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程运行同一个对象t1的不一样的方法method1和method2方法,可是这两个方法是使用同一个对象t1上进行同步的,因此实现同步的效果,侧面印证了这两种写法的一致性。app

  测试状况3:ide

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method3();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method3();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:oop

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

此次两个线程运行了不一样的类对象t1和t2的同一个方法method3,这个方法是在一个静态对象上同步,这个静态变量是在这个类的全部实例上共享的,因此也是达到了同步的效果测试

  测试状况4:

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method2();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method3();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:

A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
B synchronized loop 3
A synchronized loop 3
B synchronized loop 4
A synchronized loop 4

此次是两个线程运行了同一个对象t1的method2和method3方法,这个方法分别在t1对象和SynMethod类的静态对象上同步,因此达到同步效果

  测试状况5:

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method4();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程运行了不一样对象t1和t2的同一个方法method4,该方法是在SynMethod类上同步,实现了同步效果

  测试状况6:

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                SynMethod.staticMethod();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:

A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4

两个线程分别运行了对象t1的method4和静态方法staticMethod,这个两个方法都在SynMethod类上同步,实现了同步的效果。

  测试状况7:

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method3();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:

A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 3
B synchronized loop 4

此次两个线程运行了两个对象的method3和method4发放,这个两个方法分别在SynMethod类和SynMethod类的静态对象上同步,因此没有达到同步效果

  测试状况8:

public class SynTest {
    public static void main(String[] args) {
        final SynMethod t1 = new SynMethod();
        final SynMethod t2 = new SynMethod();
        Thread ta = new Thread(new Runnable() {
            @Override
            public void run() {
                t1.method4();
            }
        }, "A");
        Thread tb = new Thread(new Runnable() {
            @Override
            public void run() {
                t2.method2();
            }
        }, "B");
        ta.start();
        tb.start();
    }
}

运行结果:

A synchronized loop 0
B synchronized loop 0
A synchronized loop 1
B synchronized loop 1
A synchronized loop 2
B synchronized loop 2
A synchronized loop 3
B synchronized loop 3
A synchronized loop 4
B synchronized loop 4

此次两个线程运行了两个对象的method4和method2方法,这两个方法分别在SynMethod类和对象t2上同步,因此没有达到同步效果。

  使用总结:虽然上面说的状况比较多,可是从同步对象的角度,同步的场景只用三个,一个是SynMethod实例(能够多个),SynMethod的静态对象(共享)和SynMethod类(一个),只要是在同一个对象上同步,这个对象能够是实例对象,能够是静态对象,能够是类对象,那么就能够实现同步效果,不然没法达到同步,这也与synchronized设计的初衷一致。

  

2.实现原理

  首先咱们将SynMethod编译一下(命令:javac SynMethod.java),获得.class文件SynMethod.class,再经过反编译命令(javap -c SynMethod.class)

 

Compiled from "SynMethod.java"
public class concurrent.SynMethod {
  public concurrent.SynMethod();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public void method1();
    Code:
       0: aload_0       
       1: dup           
       2: astore_1      
       3: monitorenter 4: iconst_0      
       5: istore_2      
       6: iload_2       
       7: iconst_5      
       8: if_icmpge     51
      11: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      14: new           #3                  // class java/lang/StringBuilder
      17: dup           
      18: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      21: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      24: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      27: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: ldc           #8                  // String  synchronized loop 
      32: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      35: iload_2       
      36: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      39: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      42: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      45: iinc          2, 1
      48: goto          6
      51: aload_1       
      52: monitorexit   
      53: goto          61
      56: astore_3      
      57: aload_1       
      58: monitorexit   
      59: aload_3       
      60: athrow        
      61: return        
    Exception table:
       from    to  target type
           4    53    56   any
          56    59    56   any

  public synchronized void method2();
    Code:
       0: iconst_0      
       1: istore_1      
       2: iload_1       
       3: iconst_5      
       4: if_icmpge     47
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: new           #3                  // class java/lang/StringBuilder
      13: dup           
      14: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      17: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      20: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      23: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #8                  // String  synchronized loop 
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: iload_1       
      32: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      35: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      38: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      41: iinc          1, 1
      44: goto          2
      47: return        

  public void method3();
    Code:
       0: getstatic     #12                 // Field staticLockObj:Ljava/lang/Object;
       3: dup           
       4: astore_1      
       5: monitorenter  
       6: iconst_0      
       7: istore_2      
       8: iload_2       
       9: iconst_5      
      10: if_icmpge     53
      13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #3                  // class java/lang/StringBuilder
      19: dup           
      20: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      23: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      26: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: ldc           #8                  // String  synchronized loop 
      34: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      37: iload_2       
      38: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      41: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      44: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      47: iinc          2, 1
      50: goto          8
      53: aload_1       
      54: monitorexit   
      55: goto          63
      58: astore_3      
      59: aload_1       
      60: monitorexit   
      61: aload_3       
      62: athrow        
      63: return        
    Exception table:
       from    to  target type
           6    55    58   any
          58    61    58   any

  public void method4();
    Code:
       0: ldc_w         #13                 // class concurrent/SynMethod
       3: dup           
       4: astore_1      
       5: monitorenter  
       6: iconst_0      
       7: istore_2      
       8: iload_2       
       9: iconst_5      
      10: if_icmpge     53
      13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: new           #3                  // class java/lang/StringBuilder
      19: dup           
      20: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      23: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      26: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      29: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      32: ldc           #8                  // String  synchronized loop 
      34: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      37: iload_2       
      38: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      41: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      44: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      47: iinc          2, 1
      50: goto          8
      53: aload_1       
      54: monitorexit   
      55: goto          63
      58: astore_3      
      59: aload_1       
      60: monitorexit   
      61: aload_3       
      62: athrow        
      63: return        
    Exception table:
       from    to  target type
           6    55    58   any
          58    61    58   any

  public static synchronized void staticMethod();
    Code:
       0: iconst_0      
       1: istore_0      
       2: iload_0       
       3: iconst_5      
       4: if_icmpge     47
       7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: new           #3                  // class java/lang/StringBuilder
      13: dup           
      14: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      17: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      20: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
      23: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: ldc           #8                  // String  synchronized loop 
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: iload_0       
      32: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      35: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      38: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      41: iinc          0, 1
      44: goto          2
      47: return        

  static {};
    Code:
       0: new           #14                 // class java/lang/Object
       3: dup           
       4: invokespecial #1                  // Method java/lang/Object."<init>":()V
       7: putstatic     #12                 // Field staticLockObj:Ljava/lang/Object;
      10: return        
}

  经过反编译出来的代码能够看到,方法级别的synchronized同步使用了monitorenter和monitorexit这个同步命令,而monitorexit出现了两次,猜想是因为异常处理的须要

  monitorenter和monitorexit这两个命令的解释参考JVM规范:

  monitorenter :

  Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

  这段话的大概意思为:

  每一个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程以下:

一、若是monitor的进入数为0,则该线程进入monitor,而后将进入数设置为1,该线程即为monitor的全部者。

二、若是线程已经占有该monitor,只是从新进入,则进入monitor的进入数加1.

3.若是其余线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor的全部权。

 

  monitorexit: 

  The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

  这段话的大概意思为:

  执行monitorexit的线程必须是objectref所对应的monitor的全部者。指令执行时,monitor的进入数减1,若是减1后进入数为0,那线程退出monitor,再也不是这个monitor的全部者。其余被这个monitor阻塞的线程能够尝试去获取这个 monitor 的全部权。 

  经过这两段描述,咱们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是经过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为何只有在同步的块或者方法中才能调用wait/notify等方法,不然会抛出java.lang.IllegalMonitorStateException的异常的缘由。
 
  对于方法级别的同步,在flag标志中多了ACC_SYNCHRONIZED标示符,用于标记整个方法的同步,JVM在执行该方法前会读取该标记符,从而调用monitorentor命令,在退出方法时调用monitorexit命令,从而达到同步的效果(这里反编译的结果并无看到flag字段,能够参考http://www.cnblogs.com/paddix/p/5367116.html)
  
   JSR 133(Java Memory Model)FAQ(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html)中关于synchronized的问题中提到,synchronized除了保证线程对该代码块的互斥访问外,还有下面这么一段话
  But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we  release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we  acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.
  大概的意思是,同步不只仅保证互斥访问,同步还保证当前线程在同步块前和同步块中,对内存的写操做对于其余访问相同同步块(使用了同一个monitor)的线程是可见的。当咱们退出了同步块,会释放monitor,而且将缓存数据刷新到内存,这样当前线程的写操做对于其余线程是可见的,当咱们进入同步快以前,会获取monitor,而且使得当前处理器的缓存失效,从而读取数据必须从内存中从新加载,这样咱们就能够看到其余线程在同步块中写操做
 
3.总结
  Synchronized是java经常使用的同步手段,正确合理的使用是编写好并发程序的关键,本文主要是从使用和实现原理的角度来讲,下个文章将会讲讲关于synchronized的底层锁优化以,偏向锁哪些事
 
参考:
http://www.cnblogs.com/paddix/p/5367116.html
https://blog.csdn.net/sum_rain/article/details/39892219
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
相关文章
相关标签/搜索