synchronized 使用方法与实现原理

1.概述

在java中synchronized是一个非常重要的关键字,它主要用来控制线程同步的,在多线程环境下能实现临界区资源的互斥访问。

synchronized关键字最主要有以下3种应用方式,下面分别介绍

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
  • 修饰代码块,加锁对象为对象,则该类的所有的对象共享同一把锁。
2.使用案例

a.修饰实例方法

class SafeAdd implements Runnable {
    public static int num = 0;

    public synchronized void add() {
        num ++;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; ++i) {
            add();
        }
    }
}


public class SyncMethod {

    public static void main(String[] args) throws InterruptedException {
        SafeAdd safeAdd = new SafeAdd();
        Thread t1 = new Thread(safeAdd);
        Thread t2 = new Thread(safeAdd);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("SafeAdd: num = " + SafeAdd.num);
    }
}

运行结果:

SafeAdd: num = 20000

Process finished with exit code 0

b.修饰静态方法

class SafeAdd implements Runnable {
    public static int num = 0;

    public static synchronized void add() {
        num ++;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; ++i) {
            add();
        }
    }
}


public class SyncMethod {

    public static void main(String[] args) throws InterruptedException {
        SafeAdd safeAdd1 = new SafeAdd();
        SafeAdd safeAdd2 = new SafeAdd();
        Thread t1 = new Thread(safeAdd1);
        Thread t2 = new Thread(safeAdd2);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("SafeAdd: num = " + SafeAdd.num);
    }
}

运行结果:

SafeAdd: num = 20000

Process finished with exit code 0

c.同步代码块

class SafeAdd implements Runnable {
    public static int num = 0;

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 10000; ++i) {
                num ++;
            }
        }
    }
}


public class SyncMethod {

    public static void main(String[] args) throws InterruptedException {
        SafeAdd safeAdd = new SafeAdd();
        Thread t1 = new Thread(safeAdd);
        Thread t2 = new Thread(safeAdd);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("SafeAdd: num = " + SafeAdd.num);
    }
}

运行结果:

SafeAdd: num = 20000

Process finished with exit code 0

d.同步代码块,加锁对象为Class对象

class SafeAdd implements Runnable {
    public static int num = 0;

    public static void add() {
        num ++;
    }

    @Override
    public void run() {
        synchronized (SafeAdd.class) {
            for (int i = 0; i < 10000; ++i) {
                add();
            }
        }
    }
}

public class SyncMethod {
    public static void main(String[] args) throws InterruptedException {
        SafeAdd safeAdd1 = new SafeAdd();
        SafeAdd safeAdd2 = new SafeAdd();
        Thread t1 = new Thread(safeAdd1);
        Thread t2 = new Thread(safeAdd2);

        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("SafeAdd: num = " + SafeAdd.num);
    }
}

运行结果:

SafeAdd: num = 20000

Process finished with exit code 0
3.使用总结
  1. synchronized的作用对象是实例方法和普通对象时,则多个线程仅共同争抢该对象的锁。
  2. synchronized的作用对象是静态方法或一个类时,则该类所有的对象共享一个锁。
4.实现原理

synchronized的实现原理可以通过反编译代码来查看,下面看下synchronized同步代码块
如何实现同步。

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

反编译结果:
在这里插入图片描述

关于这两条指令的作用可以参考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。
  • 如果其他线程已经占用了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同步方法的反编译结果:

javap -v SynchronizedTest.class

在这里插入图片描述
从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

猜你喜欢

转载自blog.csdn.net/cl2010abc/article/details/105359278