java多线程之Synchronized关键字

引言

上一篇我们复习了线程的基本状态及三种实现方式,本篇我们继续来复习下synchronized关键字,synchronized是解决java并发问题最简单、最常用的方法之一,主要的作用是保证线程互斥的访问同步代码。

1、synchronized 原理

synchronized底层是通过一个monitor(监视器)的对象来完成的,java中每个对象都有一个monitor,monitor的初始值为0,当线程进入时monitor加1,线程退出时monitor减1,此时monitor重新回到初始值0,其它线程才能再次进入。
因此,在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。

下面我们通过反编译一段代码来看看底层的实现,java源代码:

public class SynchronizedTest {
    public void test(){
        synchronized (this){}
    }
}

反编译的字节码:

//上面省略部分字节码……
public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_1
         5: monitorexit
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
//下面省略部分字节码……

从上面的反编译字节码中,我们关注到标记3处有一个monitorenter表示获取了monitor的所有权,标记5处有一个monitorexit表示释放了monitor的所有权,可能有的同学注意到标记11处也有一个monitorexit,会不会导致monitor减2次导致为-1了?
答案肯定是不会的,我们再看看标记6处的goto,跳转到了标记14的return执行完毕返回了,不会执行到标记11,标记11处的monitorexit只有程序执行异常的时候才会执行,所以不管程序怎么执行,都会最后释放monitor的所有权。

2、对象锁和类锁

对象锁和类锁可能是开发中最容易混淆的,下面通过一个伪代码来说明下

pulbic class ObjectTest {
	//方法1
    public synchronized void syncA(){}
    //方法2
    public synchronized void syncB(){}
    //方法3
    public static synchronized void syncC(){}
    //方法4
    public static synchronized void syncD(){}
}

方法1和方法2都是对象锁,方法3和方法4都是static修饰的类锁,如果有两个实例x,y,那么下面的4个方法能否同时访问

  1. x.syncA和x.syncB
  2. x.syncA和y.syncA
  3. x.syncC和y.syncD
  4. x.syncA和ObjectTest.syncC

验证的代码限于篇幅就不贴出来了,下面是结论:

  1. 不能被同时访问。因为syncA()和syncB()都是访问同一个对象(对象x)的同步锁。
  2. 可以同时被访问。因为访问的不是同一个对象的同步锁,x.syncA()访问的是x的同步锁,而y.syncA()访问的是y的同步锁。
  3. 不能被同时访问。因为syncC()和syncD()都是static类型,x.syncC()相当于ObjectTest.syncC(),y.syncD()相当于ObjectTest.syncD(),因此它们共用一个同步锁,不能被同时反问。
  4. 可以被同时访问。因为syncA()是实例方法,x.syncA()使用的是对象x的锁;而syncC()是静态方法,ObjectTest.syncC()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。

3、总结

synchronized的优化是在jdk底层进行的,每个版本都会有差异,我们只要记住,在java中,每一个对象有且仅有一个同步锁,当我们调用某对象的synchronized方法时,就获取了该对象的同步锁,不同线程对同步锁的访问是互斥的。

原创文章 55 获赞 76 访问量 17万+

猜你喜欢

转载自blog.csdn.net/cool_summer_moon/article/details/105899594