一文彻底搞懂 volatile 和 synchronized 的区别

5 篇文章 2 订阅
本文详细比较了Java中的volatile和synchronized关键字,阐述了它们在保证变量可见性、原子性和适用场景上的差异,以及各自的优缺点和性能影响。通过实例展示了如何在实际编程中使用这两个关键字。
摘要由CSDN通过智能技术生成


在Java多线程编程中,volatile和synchronized是两个常用的关键字,用于保证共享变量的可见性和线程安全。虽然它们都可以用于实现线程安全,但是它们的运行机制和使用方式有很大不同。

1. volatile

Java中的volatile关键字用于修饰变量,表示该变量可能会被多个线程同时访问并修改,因此需要保证该变量的可见性。使用volatile修饰的变量,每次读取时都会从主内存中读取,每次修改时都会立刻写回主内存,而不会使用本地缓存。这样能够保证不同线程之间对该变量的修改是可见的。

优点:

  • 简单有效:volatile关键字使用简单,可以轻松地保证变量的可见性,而无需使用复杂的同步机制。
  • 轻量级:相比于synchronized关键字等同步机制,volatile更加轻量级,适用于简单的变量状态同步。

缺点:

  • 不能保证原子性:volatile关键字只能保证变量的可见性,但不能保证变量操作的原子性,因此在涉及到多个变量操作的情况下,仍然需要额外的同步机制来保证原子性。
  • 无法解决竞态条件:虽然volatile可以保证变量的可见性,但无法解决多线程之间的竞态条件问题,因此在一些需要原子性操作的情况下,仍然需要使用锁等同步机制来保证线程安全。

volatile关键字适用于那些不需要进行原子性操作,但需要保证可见性的变量场景,比如标识线程状态的变量、双重检查锁等。

在这里插入图片描述
下面是一个简单的示例,使用volatile保证i变量的可见性:

public class VolatileExample {
    private volatile int i = 0;

    public void increase() {
        i++;
    }

    public int getI() {
        return i;
    }

    public static void main(String[] args) {
        VolatileExample example = new VolatileExample();

        // 线程1:不断增加i的值
        Thread thread1 = new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                example.increase();
            }
        });

        // 线程2:输出i的值
        Thread thread2 = new Thread(() -> {
            while (example.getI() < 1000) {
                // 可能会一直循环等待,直到i的值被线程1修改为1000
            }
            System.out.println("i的值为:" + example.getI());
        });

        // 启动线程
        thread1.start();
        thread2.start();
    }
}

在这个示例中,我们创建了一个VolatileExample类,其中包含一个volatile修饰的变量i。在increase方法中,我们对i进行了增加操作;在getI方法中,我们返回i的当前值。在main方法中,我们创建了两个线程,线程1负责不断增加i的值,线程2负责输出i的值。由于i被volatile修饰,因此线程2可以正确地读取到线程1修改后的i的值,从而保证了变量i的可见性。

2. synchronized

Java中的synchronized关键字用于保证共享资源的互斥访问,即同一时间只有一个线程能够访问被synchronized修饰的代码块或方法。当某个线程获取到锁后,其他线程必须等待该线程释放锁之后才能继续执行。synchronized关键字可以用于保证线程安全,避免数据竞争和死锁等问题。

优点:

  • 简单有效:synchronized关键字使用简单,通过在关键代码块或方法前加上synchronized关键字,即可实现线程安全。
  • 自动释放锁:当线程执行完synchronized代码块或方法时,会自动释放锁,避免了手动管理锁的繁琐操作。

缺点:

  • 性能开销较大:由于synchronized关键字会涉及到锁的获取和释放操作,因此在高并发情况下,会存在一定的性能开销。
  • 可能导致死锁:如果在程序设计中使用不当,可能会导致死锁问题,即两个或多个线程相互等待对方持有的锁而无法继续执行。

synchronized关键字是保证线程安全的一种有效手段,特别适用于那些需要临界区保护的代码段,如对共享资源的读写操作等。然而,在使用synchronized关键字时,需要注意合理地设计锁粒度,以及避免可能导致死锁的情况发生。

下面是一个简单的示例,使用synchronized保证对count变量的操作是线程安全的:

public class SynchronizedExample {
    private int count = 0;

    // 使用synchronized修饰方法,保证线程安全
    public synchronized void increment() {
        count++;
    }

    // 使用synchronized修饰代码块,保证线程安全
    public void incrementWithSyncBlock() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();

        // 线程1:不断增加count的值,使用synchronized修饰的方法
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });

        // 线程2:不断增加count的值,使用synchronized修饰的代码块
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.incrementWithSyncBlock();
            }
        });

        // 启动线程
        thread1.start();
        thread2.start();

        // 等待两个线程执行完毕
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出count的值
        System.out.println("count的值为:" + example.getCount());
    }
}

在这个示例中,我们创建了一个SynchronizedExample类,其中包含一个count变量用于计数。我们演示了两种使用synchronized关键字的方式来保证线程安全:一种是修饰方法,另一种是修饰代码块。在main方法中,我们创建了两个线程分别执行增加count值的操作,一个线程使用synchronized修饰的方法,另一个线程使用synchronized修饰的代码块。通过synchronized关键字,我们可以确保两个线程对count的操作是线程安全的,避免了数据竞争的问题。

3. volatile和synchronized的区别

1.可见性:

  • volatile关键字主要是用于保证共享变量的可见性。当多个线程同时访问某个共享变量时,如果不加volatile关键字,则可能会出现多个线程同时修改该变量但是其中的某些线程没有看到其他线程对该变量的修改。加上volatile后,该变量的值将被及时更新到主内存中,使得所有线程都能看到最新的值。
  • synchronized关键字不仅保证了可见性,还保证了原子性和有序性。除了将变量从私有内存同步到主内存外,synchronized还会将线程工作内存中的数据与主内存中的数据进行同步,同时保证了代码块的原子性和有序性。

2.原子性:

  • volatile关键字仅保证了被修饰变量的读写操作是原子性的。但是,对于复合操作(例如i++),volatile无法保证原子性,因为这是一个复合操作,包括读取、修改、写入三个步骤,并不能保证这三个步骤的连续性。
  • synchronized关键字可以保证被修饰代码块的原子性,即同一时间只有一个线程能够执行该代码块,其他线程需要等待。

3.适用范围:

  • volatile关键字适用于只有一个线程写,多个线程读的场景,例如标识位、状态标志等。
  • synchronized关键字适用于临界区代码,需要保证原子性和有序性的场景,例如对共享资源的读写操作。

4.性能开销:

  • volatile关键字的性能开销相对较低,因为它仅仅是对变量的可见性进行了保证。
  • synchronized关键字的性能开销相对较高,因为它需要在进入和退出临界区时获取和释放锁,并且涉及到线程的上下文切换。
  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,synchronizedvolatile是用来实现多线程同步的关键字。它们在实现多线程同步的机制上有一些不同之处。 首先,synchronized关键字提供了互斥访问的能力,即同一时刻只允许一个线程访问被synchronized修饰的代码块或方法。它可以解决并发编程中的原子性、可见性和有序性问题。通过获取锁来实现同步,保证了代码的原子性和有序性。同时,synchronized也保证了共享变量在不同线程之间的可见性。 而volatile关键字主要用来解决共享变量的可见性和有序性问题。使用volatile修饰的变量会强制线程从主内存中读取变量的最新值,而不是使用缓存中的旧值。这样可以确保一个线程对volatile变量的修改对其他线程是可见的。然而,volatile不能保证原子性,因此在执行复合操作时仍然需要加锁来保证原子性。 此外,synchronized关键字可以用于修饰代码块或方法,而volatile只能修饰变量。另外,synchronized是悲观锁,会导致线程阻塞等待锁的释放,而volatile是乐观锁,不会阻塞线程,而是要求线程自己通过CAS操作来保证数据一致性。 综上所述,synchronizedvolatile关键字在实现多线程同步的机制上有一些不同。synchronized提供了更全面的多线程同步能力,可以解决原子性、可见性和有序性问题,但会引入线程阻塞。而volatile关键字主要解决共享变量的可见性和有序性问题,但无法保证原子性。因此,在选择使用synchronized还是volatile时,需要根据具体的应用场景和需求进行权衡。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [synchronizedvolatile区别是什么?](https://blog.csdn.net/m0_68464502/article/details/131107637)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [4个点说清楚Javasynchronizedvolatile区别](https://blog.csdn.net/wanghao112956/article/details/100133457)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java关键字volatilesynchronized作用和区别](https://download.csdn.net/download/weixin_38557838/12778536)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值