新手向同步关键字synchronized对this、class、object、方法的区别

在看源代码时遇到多线程需要同步的时候,总是会看见几种写法,修饰方法、修饰静态方法、synchronized(Xxx.class)synchronized(this)synchronized(obj),之前一直没深究几种方式的区别,现在想来真是惊出一身冷汗,居然这个问题都没有仔细想清楚。

synchronized的语义

每个对象都有一个监视器monitor,被synchronized修饰,语义就是获取这个对象的monitor,反编译后可以看到monitorenter和monitorexit。synchronized关键字有三种应用方式(其实按标题来讲应该是5种,但是其中有两种都是与另外两种等价的):

  • 修饰实例方法
  • 修饰静态方法
  • 修饰代码块(指定对象)

实验

先上代码做实验验证一下

public class SyncThread {
    private final Object lock = new Object();

    public void foo() throws Exception {
        synchronized (SyncThread.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(">>>foo: " + i);
                Thread.sleep(1000);
            }
        }
    }

    public void bar() throws Exception {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println("<<<bar: " + i);
                Thread.sleep(1000);
            }
        }
    }

    public void cpp() throws Exception {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                System.out.println("===cpp: " + i);
                Thread.sleep(1000);
            }
        }
    }

    public void der() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println("!!!der: " + i);
            Thread.sleep(1000);
        }
    }
}

可以看到有四种不同的synchronized修饰,以及一个没有同步的方法,再运行一下看看结果

public class ThreadApp {

    public static void main(String[] args) {
        final SyncThread syncThread = new SyncThread();
        new Thread(() -> {
            try {
                syncThread.foo();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                syncThread.bar();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                syncThread.cpp();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                syncThread.der();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

可以看到结果

>>>foo: 0
<<<bar: 0
===cpp: 0
!!!der: 0
>>>foo: 1
===cpp: 1
<<<bar: 1
!!!der: 1
>>>foo: 2
!!!der: 2
<<<bar: 2
===cpp: 2
>>>foo: 3
<<<bar: 3
===cpp: 3
!!!der: 3
>>>foo: 4
!!!der: 4
<<<bar: 4
===cpp: 4

分析

从以上结果来看各线程并没有发生竞争,互不影响,其实明白了synchronized语义也很好理解以上结果,几个synchronized获取的monitor都不是一个,当然相互不影响。
但是值得注意的几点:

  • synchronized(Xxx.class)获取的是类的monitor,所以与public synchronized static void some()修饰静态方法是等价的
  • synchronized(this)获取的是当前实例的monitor,所以与public synchronized void some()修饰实例方法是等价的

以上两点可以通过修改上述代码中方法可以很容易验证,我们修改最后一个方法

public synchronized static void der() throws Exception {
    for (int i = 0; i < 5; i++) {
        System.out.println("!!!der: " + i);
        Thread.sleep(1000);
    }
}

运行得到结果

!!!der: 0
!!!der: 1
!!!der: 2
!!!der: 3
!!!der: 4
>>>foo: 0
>>>foo: 1
>>>foo: 2
>>>foo: 3
>>>foo: 4

可以看到,确实synchronized(Xxx.class)synchronized修饰静态方法是等价的。再修改为synchronized修饰实例方法

public synchronized void der() throws Exception {
    for (int i = 0; i < 5; i++) {
        System.out.println("!!!der: " + i);
        Thread.sleep(1000);
    }
}

运行查看结果

<<<bar: 0
<<<bar: 1
<<<bar: 2
<<<bar: 3
<<<bar: 4
!!!der: 0
!!!der: 1
!!!der: 2
!!!der: 3
!!!der: 4

可以看到,确实synchronized(this)synchronized修饰实例方法是等价的。
总之,个人认为要理解几种不一样的地方,关键是理解清楚是获取的谁的monitor,只要是同一个monitor,当然就会发生同步!



许多年前 你有一双清澈的双眼

奔跑起来 像是一道春天的闪电

想看遍这世界 去最遥远的远方

感觉有双翅膀 能飞越高山和海洋




原文地址:https://www.cnblogs.com/1024Community/p/8711608.html