JUC解析-synchronized

本文详细介绍了Java中的synchronized关键字,包括其三种使用场景、实现原理及属性,如可重入性和内存可见性,并讨论了死锁问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面主要讲了基于简单的基本类型的原子操作,对于一些复杂的操作就需要用锁来实现互斥,在JUC中定义了一套锁的实现框架,但是在学习前,感觉有必要先回顾了java中自带的锁synchronized。

介绍

synchronized的作用如下

  • 确保线程互斥的访问同步代码(互斥性)。

  • 保证共享变量的修改能够及时可见(可见性)。

  • 有效解决重排序问题(有序性)。

在这里主要讲解下Synchronized在作用1的实现原理以及使用的方式,对于Synchronized主要有三个使用场景:

  • 修饰普通方法

  • 修饰静态方法

  • 修饰代码块

举个栗子:

public class Counter {  
    private static int count;
    private Object lock = new Object(); 
     
    public synchronized void thisIncr1() {
        count ++;
    }  
    public static synchronized void classIncr1() {
        count++;
    }  
    public void lockIncr() {    
        synchronized(lock) {
            count++;
        }
    }  
    public void thisIncr2(){     
        synchronized(this) {
            count ++;
        }
    }
    public synchronized int getCount() {
        return count;
    }  
    public void classIncr2() {    
        synchronized(Counter.class) {
            count++;
        }
    }
}
复制代码

如上thisIncr1中修饰的普通方法,classIncr1中修饰的静态方法,thisIncr2和classIncr2修饰的是代码块,上面的同步逻辑有什么区别呢?

synchronized保护的是对象而非代码,只要是访问同一个对象的synchronized方法,即使是不同的代码,也会被同步顺序访问,在上面的代码中thisIncr1和thisIncr2都是同步的this对象。classIncr1和classIncr2同步的Counter.class对象。lockIncr同步的是lock对象。从上面可以确定thisIncr*、classIncr*、lockIncr相互之间是没有互斥同步关系的,只有在他们本身类型之间才会同步执行, 具体可以自行写测试用例测试下。

synchronized的实现原理

举个栗子:

public class SynchronizedDemo {    
    public void method() {        
        synchronized (this) {
          System.out.println("hello word");
        }
    }
}
复制代码

通过javap反编译后为:

通过反编译后的代码,可以看到在synchronized开始和结束的时候分别执行了monitorenter和monitorexit,在java中每个对象有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。

  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

从上面分析可以看出,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

synchronized的属性

  • 可重入性
    对同一个执行线程,它在获得了锁之后,在调用其他需要同样锁的代码时,可以直接调用,比如说,在一个synchronized实例方法内,可以直接调用其他synchronized实例方法。可重入是一个非常自然的属性,应该是很容易理解的,之所以强调,是因为并不是所有锁都是可重入的。

  • 内存可见性
    线程在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读最新数据。如果仅仅是为了数据的可见性可以用volitile来代替,成本会小很多。

  • 死锁
    使用synchronized或者其他锁,要注意死锁,所谓死锁就是类似这种现象,比如, 有a, b两个线程,a持有锁A,在等待锁B,而b持有锁B,在等待锁A,a,b陷入了互相等待,最后谁都执行不下去,解决的办法就是首先,应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁,顺便说一下,如果程序出现了死锁可以通过pstack命令来查看进程的状态来发现死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值