前言:

在并发编程中会存在线程安全问题, 因为存在多线程共同操作共享数据的可能性。关键字 Synchronized 可以保证在同一个程序,同一时刻,只有一个线程可以执行某个方法或某个代码块,同时 Synchronized 可以保证一个线程的变化可见(可见性),可以代替 volatile。

基本概念

  1. Synchronized 是JAVA 中解决并发问题最常用的方法,也是最简单的一种方法。
  2. Synchronized 的作用共有三个:
    a. 原子性: 确保线程互斥的访问同步代码;
    b. 可见性:保证共享变量的修改能够及时可见,其实是通过java 内存模型中的 “对一个变量 unlock 操作之前,必须同步到主内存中;如果对一个变量进行 lock 操作,则将会清空工作内存中此变量的值,在这行引擎使用此变量钱,需要重新从主内存中 load 操作或 assign 操作初始化变量值” 来保证的。
    c. 有序性: 有效解决重排序问题,即 “一个 unlock 操作先行发生于后面对用一个loack操作
  3. 从语法上将, Synchronized 可以把任何一个非 null 对象作为"锁", 在 HotSpot JVM 实现中,锁有个专门的名字: 对象监视器(Object Monitor
基本使用:

Synchronized 共有三种用法:

  1. 当作用在对象方法时:监视器(monitor)便是对象实例(this)
  2. 当作用在静态方法时:监视器(monitor)便是对象的 class 实例,因为 class 数据存在于永久代,因此静态方法锁相当于该类的一个全局锁
  3. 当作用在某一个对象实例时: 监视器(monitor)便是括号括起来的对象实例
作用在对象方法时:
  1. Synchronized 用于类的普通方法上时,监视器(monitor)便是对象实例(this):当多个线程同时调用对象的被 Synchronized修饰的普通方法时,需要先获取当前的对象锁,才可以继续往下执行,当一个线程获取到对象锁时,其他线程就不能访问Synchronized示例对象,但是可以访问非 Synchronized 修饰的方法。
  2. 示例:
public class SynchronizedTest {

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();
        // 线程一
        new Thread(new Runnable() {
            public void run() {
                test.methodA();
            }
        }).start();

        // 线程二
        new Thread(new Runnable() {
            public void run() {
                test.methodA();
            }
        }).start();

        // 线程三
        new Thread(new Runnable() {
            public void run() {
                test.methodB();
            }
        }).start();

        // 线程四
        new Thread(new Runnable() {
            public void run() {
                test.methodC();
            }
        }).start();
    }

    // synchronized
    public synchronized void methodA(){
        try {
            System.out.println(Thread.currentThread().getName() +"开始执行 MethodA");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() +"结束执行 MethodA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // synchronized
    public synchronized void methodB(){
        try {
            System.out.println(Thread.currentThread().getName() +"开始执行 MethodB");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() +"结束执行 MethodB");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 普通方法
    public void methodC(){
        try {
            System.out.println(Thread.currentThread().getName() +"开始执行 MethodC");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() +"结束执行 MethodC");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

执行结果:

Thread-0开始执行 MethodA
Thread-3开始执行 MethodC
Thread-0结束执行 MethodA
Thread-2开始执行 MethodB
Thread-3结束执行 MethodC
Thread-2结束执行 MethodB
Thread-1开始执行 MethodA
Thread-1结束执行 MethodA

解释:

  1. 线程 Thread-0、1、2 分别调用同一个实例对象 test 中 synchronized 修饰的 methodA、methodA、methodB 线程1,2,3 需要依次获取到对象锁,然后执行对应的方法。
  2. 线程 Thread-3 由于执行的是普通方法,因此 Thread-0、1、2 对其执行并没有产生影响。
作用在静态方法时:
  1. 作用在静态方法上时,监视器作用的是 Class ,锁是Class,多线程运行时需要获取到当前的 Class 锁才可以继续执行。
  2. 例子:
public class SynchronizedTest {

    public static void main(String[] args) {
        // 线程一
        new Thread(new Runnable() {
            public void run() {
                SynchronizedTest.methodA();
            }
        }).start();

        // 线程二
        new Thread(new Runnable() {
            public void run() {
                SynchronizedTest.methodA();
            }
        }).start();

        // 线程三
        new Thread(new Runnable() {
            public void run() {
                SynchronizedTest.methodB();
            }
        }).start();

        // 线程四
        new Thread(new Runnable() {
            public void run() {
                SynchronizedTest.methodC();
            }
        }).start();
    }

    // synchronized 静态方法
    public static synchronized void methodA(){
        try {
            System.out.println(Thread.currentThread().getName() +"开始执行 static MethodA");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() +"结束执行 static MethodA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // synchronized 静态方法
    public static synchronized void methodB(){
        try {
            System.out.println(Thread.currentThread().getName() +"开始执行 static MethodB");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() +"结束执行 static MethodB");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 普通静态方法
    public static void methodC(){
        try {
            System.out.println(Thread.currentThread().getName() +"开始执行 static MethodC");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() +"结束执行 static MethodC");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
  1. 打印结果:
Thread-0开始执行 static MethodA
Thread-3开始执行 static MethodC
Thread-0结束执行 static MethodA
Thread-3结束执行 static MethodC
Thread-2开始执行 static MethodB
Thread-2结束执行 static MethodB
Thread-1开始执行 static MethodA
Thread-1结束执行 static MethodA

解释:

  1. 线程 Thread-0、1、2 分别调用同一个 SynchronizedTest 类中 synchronized 修饰的静态 methodA、methodA、methodB 线程1,2,3 需要依次获取到类锁,然后执行对应的方法。
  2. 线程 Thread-3 由于执行的是普通静态方法,因此 Thread-0、1、2 对其执行并没有产生影响。
作用在某一个对象实例 (比较常用):
  1. 当 synchronized 作用于到某个对象时,多个线程调用时需要获取到作用的对象锁才可以执行,其他线程进入等待待其释放对象锁后,获取到并执行。
  2. 例子:
public class SynchronizedTest {

    // 对象锁
    private Object lock = new Object();

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        // 线程一
        new Thread(new Runnable() {
            public void run() {
                test.methodA();
            }
        }).start();

        // 线程二
        new Thread(new Runnable() {
            public void run() {
                test.methodB();
            }
        }).start();

    }

    public  void methodA(){
        synchronized (lock){
            try {
                System.out.println(Thread.currentThread().getName() +"开始执行 MethodA");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() +"结束执行 MethodA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void methodB(){
        synchronized (lock) {
            try {
                System.out.println(Thread.currentThread().getName() + "开始执行 MethodB");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束执行 MethodB");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.执行结果:

Thread-0开始执行 MethodA
Thread-0结束执行 MethodA
Thread-1开始执行 MethodB
Thread-1结束执行 MethodB
  1. 解释,实例中定义了对象Object,线程一二 在执行到代码 synchronized (lock) 回去获取 或者 等待获取对象 lock 锁才可以继续往下执行。
  2. 在并发编程中,此方法较为常用,用于给几行比较核心的代码进行加锁,实现并发安全的问题。

synchronized 实现的原理: 待完成。

End: