首页 > 技术文章 > JAVA并发编程:synchronized及其实现原理

lynn16 2019-04-14 00:49 原文

  • 一、synchronized的基本使用

  synchronized是Java中解决并发问题的一种最常用的方法,也就最简单的一种方法。synchronized的作用有以下三个:

(1)确保线程互斥的访问同步代码

(2)保证共享变量的修改能及时可见

(3)有效解决重排序问题

从语法上将,synchronized总用有三种用法:

(1)修饰普通方法

(2)修饰静态方法

(3)修饰静态代码块

1、没有同步的情况:

  • 代码段1
    public class NoSynchronized {
    
        public void method1() {
            System.out.println("Method 1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 1 end");
        }
    
        public void method2() {
            System.out.println("Method 2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 2 end");
        }
    
        public static void main(String[] args) {
            final NoSynchronized bean = new NoSynchronized();
    
            new Thread(new Runnable() {
                public void run() {
                    bean.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                public void run() {
                    bean.method2();
                }
            }).start();
        }
    }

     执行结果如下,线程1和线程2同时进入执行状态,线程2要比线程1快,所以线程2先执行完成,这个过程线程1和线程2是同时执行的。

2、对普通方法同步

  •  代码段二
    public class NormalMethodSynchronized {
    
        public synchronized void method1() {
            System.out.println("Method 1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 1 end");
        }
    
        public synchronized void method2() {
            System.out.println("Method 2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 2 end");
        }
    
        public static void main(String[] args) {
            final NormalMethodSynchronized bean = new NormalMethodSynchronized();
    
            new Thread(new Runnable() {
                public void run() {
                    bean.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                public void run() {
                    bean.method2();
                }
            }).start();
        }
    }

 执行结果如下,跟代码一比较,可以很明显看出,线程2需要等待线程1的method1执行完成才能执行method2方法。

 

3、静态方法(类)同步

  • 代码段3
    public class StaticMethodSynchronized {
        public static synchronized void method1() {
            System.out.println("Method 1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 1 end");
        }
    
        public static synchronized void method2() {
            System.out.println("Method 2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 2 end");
        }
    
        public static void main(String[] args) {
            final StaticMethodSynchronized bean1 = new StaticMethodSynchronized();
            final StaticMethodSynchronized bean2 = new StaticMethodSynchronized();
    
            new Thread(new Runnable() {
                public void run() {
                    bean1.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                public void run() {
                    bean2.method2();
                }
            }).start();
        }
    }

 执行结果如下,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象的方法),所以即使bean1和bean2属于不同的对象,但是他们都属于StaticMethodSynchronized类的实例,所以也只能顺序的执行method1和method2,不能并发执行。

4、代码块同步

  • 代码段
    public class SegmentSynchronized {
    
        public void method1() {
            System.out.println("Method 1 start");
            try {
                synchronized (this) {
                    Thread.sleep(300);
                    System.out.println("Method 1 execute");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 1 end");
        }
    
        public void method2() {
            System.out.println("Method 2 start");
            try {
                synchronized (this) {
                    Thread.sleep(100);
                    System.out.println("Method 2 execute");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 2 end");
        }
    
        public static void main(String[] args) {
            final SegmentSynchronized bean = new SegmentSynchronized();
            new Thread(new Runnable() {
                public void run() {
                    bean.method1();
                }
            }).start();
            new Thread(new Runnable() {
                public void run() {
                    bean.method2();
                }
            }).start();
        }
    }

     执行结果如下,虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成

二、synchronized原理

可通过反编译查看synchronized是如何实现代码块同步的,java代码如下:

public class SynchronizedDemo {

    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

 反编译结果:

关于这两条指令,直接参看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被占用时就会处理锁定状态。线程执行monitorente是尝试获取monitor的所有权,过程如下:monitorexit
    1. 如果monitor的进入数为0,则该线程进入monitor并且设置进入数为1,当前线程占有monitor的所有权。
    2. 如果当前线程占有monitor的所有权,重新进入monitor,则进入montior的进入数为1
    3. 如果其他线程占有monitor的所有权,则当前线程进入阻塞状态直到monitor进入数为0,然后尝试获取monitor所有权。
  • 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指令时必须是Object所对应的monitor所有者。

    指令执行时monitor进入数减一,如果减一后结果为0,当前线程退出monitor并且不再是这个monitor的所有者,其他处理阻塞状态的线程允许进入monitor获取所有权。

通过上面两个指令可以看出synchronized实现原理,其实wait/notify也依赖于monitor对象。

同步方法的反编译结果:

  • 源代码:
    public class SynchronizedMethod {
    
        public synchronized void method() {
            System.out.println("Hello World!");
        }
    }

     反编译结果

 

  从反编译结果来看,方法的同步没有通过monitorenter和monitorexit来完成,不过相对于普通方法,其常量池中多了一个ACC_SYNCHRONIZED标识符。JVM就是根据该标识符来实现方法同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标识是否被设置了,如果设置了执行线程先获取monitor,获取成功之后才能执行方法体,方法执行完后在释放monitor。在方法访问期间,其他线程都无法在获取同一个monitor对象。其实本质上没有区别,只是方法的同步是一种隐式的方法来实现。

三、运行结果解析

1、代码段2结果:虽然method1和mothod2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序执行。

2、代码段3结果:虽然bean1和bean2属于不同的对象,但是bean1和bean2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候都需要获取同一个类上的monitor(每个类只对应一个class对象),所以也只能顺序执行。

3、代码段4结果:对于代码块同步实质上需要获取synchronized关键字后面括号中对象的monitor,有个这段代码中括号的内容都是this,而method1和method2又是通过同一对象去调用的,所以同步代码快之前需要竞争同一对象上的锁,因此也只能顺序执行。

推荐阅读