Java并发编程:Synchronized及其实现原理

 
1、Synchronized的基本使用

    Synchronized是Java中解决并发问题的一种最经常使用的方法,也是最简单的一种方式。结合上篇《Java并发编程:核心理论》的论述Synchronized的主要做用分为如下三个:java

  1. 保证线程互斥的访问同步代码
  2. 保证共享变量的修改的可见性
  3. 有效的解决重排序

从语法的角度讲,Synchronized又三种如下用法:编程

  1. 修饰普通方法
  2. 修饰静态方法
  3. 修饰代码块

下面同个几个代码段来看看这三种方式在有Synchronized和没有下的状况:安全

一、普通方法没有同步的状况:并发

public class studentSychronized {

    public void method_1(){
        System.out.println("方法1启动。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法1执行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1结束。。。");
    }
    public void method_2(){
        System.out.println("方法2启动。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法2执行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2结束。。。");
    }

    public static void main(String[] args) {
        studentSychronized student = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_2();
            }
        }).start();

    }
}

执行的结果以下,两个线程执行的方法同时开始启动,线程1执行的比线程2快,因此先结束。多执行几回会发现输出的结果有可能线程1比线程2快,也有可能线程2比线程1快。能够发现两个线程是交替执行的。ide

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

二、对方法加Sychronized修饰,同步执行this

public class studentSychronized {

    public synchronized void method_1(){
        System.out.println("方法1启动。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法1执行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1结束。。。");
    }
    public synchronized void method_2(){
        System.out.println("方法2启动。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法2执行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2结束。。。");
    }

    public static void main(String[] args) {
        studentSychronized student = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_2();
            }
        }).start();

    }
}

执行的结果以下,能够看到线程2是等待线程1执行完成后才开始执行。为何加上sychronized会出现这种结果,咱们先按下不表,接着往下看。线程

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

三、静态方法(类)同步code

public class studentSychronized {

    public static synchronized void method_1(){
        System.out.println("方法1启动。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法1执行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1结束。。。");
    }
    public static synchronized void method_2(){
        System.out.println("方法2启动。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法2执行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2结束。。。");
    }

    public static void main(String[] args) {
        studentSychronized student = new studentSychronized();
        studentSychronized student1 = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student1.method_2();
            }
        }).start();

    }
}

    执行结果以下,对静态方法的同步本质上是对类的同步(由于static关键字的特效,静态方法本质是上属于类的方法,而不是对象的方法),因此即便同一个类的不一样的实例也只能顺序执行,不能并发执行。对象

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

四、代码块同步blog

public class studentSychronized {

    public  void method_1(){


        try {
            synchronized (this){
                System.out.println("方法1启动。。。");
                System.out.println("方法1执行中。。。");
                Thread.sleep(3000);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1结束。。。");
    }
    public  void method_2(){
        System.out.println("方法2启动。。。");
        try {
            synchronized (this){
                Thread.sleep(3000);
                System.out.println("方法2执行中。。。");
            }


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2结束。。。");
    }

    public static void main(String[] args) {
         studentSychronized student = new studentSychronized();
         studentSychronized student1 = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student1.method_2();
            }
        }).start();

    }
}

执行结果以下,虽然两个线程是同时开始执行,可是在线程2进入同步代码块的时候先是等待线程1的代码块执行完后继续执行。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

2、Synchronized工做原理

    带着上述代码执行的结果的疑问,咱们先来了解下Synchronized的原理。等了解完原理,我相信这些疑问会迎刃而解。首先经过反编译下面的代码来看看Synchronized是如何进行同步的:

代码:

public class sychronizedDemo {
    public void method(){
        synchronized (this){
            System.out.println("HELLO !!!");
        }
    }
}

反编译结果:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

在反编译结果中咱们发现有两个指令:monitorenter/monitorexit.咱们看看这两条指令JVM规范中是怎么描述的。

JVM规范中对monitorenter和monitorexit指令的描述以下:
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的异常的缘由。

接下来咱们再来看一段代码:

public class SynchronizedMethod {
     public synchronized void method() {
         System.out.println("Hello World!");
    }
 }

反编译结果

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

    从反编译的结果咱们能发现,普通方法的同步并无经过指令 monitorenter和monitorexit 来完成,不过同步方法比普通方法在常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标识符来实现方法的同步的:

    当方法被调用时,调用指令会检查方法的ACC_SYNCHRONIZED访问标识是否被设置,若是设置了,执行线程将先获取该方法的monitor对象,获取成功以后才能执行方法体,方法执行完后再释放monitor对象。在方法执行期间,其余线程都是没法再次获取同一个minitor对象。其实本质上没有区别,只是在方法的同步时换了一种方式来实现。

    了解了synchronized的工做原理,咱们能够在回过头去看看咱们开头的代码,执行结果的原理我相信你们都一目了然了。

三:总结

      Synchronized是Java并发编程中最经常使用的用于保证线程安全的方式,其使用相对也比较简单。可是若是可以深刻了解其原理,对监视器锁等底层知识有所了解,一方面能够帮助咱们正确的使用Synchronized关键字,另外一方面也可以帮助咱们更好的理解并发编程机制,有助咱们在不一样的状况下选择更优的并发策略来完成任务。对平时遇到的各类并发问题,也可以从容的应对。

相关文章
相关标签/搜索