一、Synchronized的基本使用

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

  1. 保证线程互斥的访问同步代码

  2. 保证共享变量的修改的可见性

  3. 有效的解决重排序

从语法的角度讲,Synchronized又三种以下用法:

  1. 修饰普通方法

  2. 修饰静态方法

  3. 修饰代码块

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

1、普通方法没有同步的情况:

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快。可以发现两个线程是交替执行的。

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

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会出现这种结果,我们先按下不表,接着往下看。

3、静态方法(类)同步

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关键字的特效,静态方法本质是上属于类的方法,而不是对象的方法),所以即使同一个类的不同的实例也只能顺序执行,不能并发执行。

4、代码块同步

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的代码块执行完后继续执行。

二、Synchronized工作原理

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

代码:

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

反编译结果:

在反编译结果中我们发现有两个指令: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的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该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!");
    }
 }

反编译结果

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

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

    了解了synchronized的工作原理,我们可以在回过头去看看我们开头的代码,执行结果的原理我相信大家都一目了然了。

三:总结

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


点赞(0)

评论列表 共有 0 条评论

暂无评论
立即
投稿
发表
评论
返回
顶部