深入解析Java中的synchronized关键字

本文详细介绍了Java中的synchronized关键字,包括其作用、使用方式、实现原理以及锁的升级过程。synchronized用于保证多线程环境下的线程安全,通过对象头的MarkWord实现锁的管理。锁升级从无锁到偏向锁、轻量级锁再到重量级锁,以适应不同并发场景,降低锁开销。此外,文章还讨论了synchronized与ReentrantLock的区别,以及在使用中需要注意的事项,如锁的正确使用、避免死锁等问题。通过对锁的深入理解,有助于提升Java并发编程的效率和安全性。

写在文章开头

在多线程编程中,确保数据的一致性和线程安全是至关重要的问题。Java 语言提供了多种机制来实现这一目标,其中 synchronized 关键字是最常用且最基础的一种同步工具。本文将通过具体的使用示例和详细的底层原理分析,帮助读者全面理解 synchronized 的工作方式及其在实际开发中的应用。

在这里插入图片描述

Hi,我是 sharkChili ,是个不断在硬核技术上作死的技术人,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

同时也非常欢迎你star我的开源项目mini-redis:https://github.com/shark-ctrl/mini-redis

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

synchronized是什么?有什么用?

synchronized是在多线程场景经常用到的关键字,通过synchronized将共享资源设置为临界资源,确保并发场景下共享资源操作的正确性:

在这里插入图片描述

synchronized基础使用示例

作用于静态方法

synchronized作用于静态方法上,锁的对象为Class,这就意味着方法的调用者无论是Class还是实例对象都可以保持互斥,所以下面这段代码的结果为200


public class SynchronizedDemo {
   
   
private static Logger logger = LoggerFactory.getLogger(SynchronizedDemo.class);

    private static int count = 0;

    /**
     * synchronized作用域静态类上
     */
    public synchronized static void method() {
   
   
        count++;
    }

   @Test
    public  void test() {
   
   
        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->SynchronizedDemo.method());

        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->new SynchronizedDemo().method());


        logger.info("count:{}",count);

    }

}

输出结果

22:59:44.647 [main] INFO com.sharkChili.webTemplate.SynchronizedDemo - count:20000

作用于对象方法

作用于方法上,则锁住的对象是调用的示例对象,如果我们使用下面这段写法,最终的结果却不是10000。



    private static Logger logger = LoggerFactory.getLogger(SynchronizedDemo.class);

    private static int count = 0;

    /**
     * synchronized作用域实例方法上
     */
    public synchronized  void method() {
   
   
        count++;
    }

    @Test
    public  void test() {
   
   

        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->new SynchronizedDemo().method());


        logger.info("count:{}",count);

    }
}

输出结果

2023-03-16 21:03:44,300 INFO  SynchronizedDemo:30 - count:8786

因为synchronized 作用于实例方法,会导致每个线程获得的锁都是各自使用的实例对象,而++操作又非原子操作,导致互斥失败进而导致数据错误。
什么是原子操作呢?通俗的来说就是一件事情只要一条指令就能完成,而count++在底层汇编指令如下所示,可以看到++操作实际上是需要3个步骤完成的:

  1. 从内存将count读取到寄存器
  2. count自增
  3. 写回内存
__asm
{
   
   
        moveax,  dword ptr[i]
        inc eax
        mov dwordptr[i], eax
}

正是由于锁互斥的失败,导致两个线程同时到临界区域加载资源,获得的count都是0,经过自增后都是1,导致数据少了1。

在这里插入图片描述

所以正确的使用方式是多个线程使用同一个对象调用该方法

SynchronizedDemo demo = new SynchronizedDemo();
        IntStream.rangeClosed(1,1_0000)
                .parallel()
                .forEach(i->demo.method());


        logger.info("count:{}",count);

这样一来输出的结果就正常了。

2023-03-16 23:08:23,656 INFO  SynchronizedDemo:31 - count:10000

作用于代码块

作用于代码块上的synchronized锁住的就是括号内的对象实例,以下面这段代码为例,锁的就是当前调用者:

//锁住当前调用实例
public void method() {
   
   
        synchronized (this) {
   
   
            count++;
        }
    }

所以我们的使用的方式还是和作用与实例方法上一样:



        SynchronizedDemo demo = new SynchronizedDemo();
        IntStream.rangeClosed(1, 1_0000)
                .parallel()
                .forEach(i -> demo.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shark-chili

您的鼓励将是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值