何时使用Volatile修饰符?
何时使用Volatile修饰符?
可能是重复问题:
什么时候和为什么需要使用volatile修饰符在Java中?
我对看到使用volatile修饰的原始类型或对象引用的现实世界用法很感兴趣。
在多线程程序中,使用volatile关键字可以告诉JVM要谨慎处理并发运行的线程。实际上,volatile用于指示变量的值将被不同的线程修改。
声明一个volatile的Java变量意味着:
- 这个变量的值不会在本地线程中被缓存,所有的读写操作都会直接访问“主内存”。
- 对变量的访问就好像它被封装在一个同步块中,并且同步于自身。
我们在第二点中说“就像”是因为对于程序员来说(以及可能在大多数JVM实现中),实际上没有涉及任何锁对象。
volatile关键字告诉编译器,被volatile修饰的变量可以被程序的其他部分意外地改变。其中一个情况涉及多线程程序。
在多线程程序中,有时两个或更多的线程共享同一个实例变量。出于效率考虑,每个线程可以保留它自己的私有副本的这样一个共享变量。
变量的真实(或主)副本在不同的时间更新,比如当进入同步方法时。虽然这种方法工作得很好,但有时效率可能不高。在某些情况下,真正重要的是主副本始终反映其当前状态。
为了确保这一点,只需将变量指定为volatile,告诉编译器它必须始终使用volatile变量的主副本(或者至少始终将任何私有副本与主副本保持同步)。对主变量的访问必须按照其在任何私有副本上执行的顺序执行。
“变量的访问就好像它被封装在一个同步块中,并且同步于自身。”这让人觉得myVolatile++
就像synchronized (something) { myVolatile++ }
一样,但实际上并不是这样。
它只是对程序员的一个提示...没有涉及任何锁...
所以在使用volatile修饰的多线程情况下,不会维护变量的线程本地副本吗?
变量可能有一个线程本地副本... volatile
关键字只是一个提示给JVM,在同步本地副本与主副本时要谨慎。
它不仅仅是一个提示 - 它建立了JVM必须遵守的操作之间的正式关系,并且(在Java 5+中)它影响不止一个volatile字段。
必须,我之前不知道...谢谢。
Volatile修饰符在以下情况下非常有用:当你想要确保在一个线程中对某个字段的写操作和另一个线程对该字段的后续读操作之间存在一个内存屏障,即一个形式上的“先行发生”关系。同步也可以提供这种关系,以及其他多线程保证,但它会稍微慢一些并且可能会创建同步瓶颈。
一个使用场景是在并发集合类中(如ConcurrentHashMap或LinkedBlockingQueue)中,结合原子比较和交换(CAS)操作,你可以编写正确的线程安全代码,而无需使用synchronized关键字。
需要注意的是,上述回答是基于Java 5及以上版本的,对于Java 1.4及以下版本,volatile并不会强制实施先行发生关系。
解决方法:使用volatile修饰符可以解决在多线程环境下的一些问题。它可以确保字段的可见性,并且可以在写操作和读操作之间提供内存屏障,从而确保正确的线程同步。在一些特殊情况下,volatile修饰符可以替代synchronized关键字,提供更高效的线程安全性。但需要注意的是,volatile不能保证原子性,所以在需要原子操作的情况下,仍然需要使用其他的同步手段。
下面是一个使用volatile修饰符的示例代码:
public class VolatileExample { private volatile int count = 0; public void increment() { count++; } public int getCount() { return count; } }
在上面的代码中,count字段被声明为volatile,这样在多个线程中对increment()方法的并发调用时,可以保证每次调用都能正确地增加count的值,并且在其他线程中能够立即看到这个变化。
使用volatile修饰符可以确保字段的可见性和正确的线程同步,但它并不能保证原子性。在合适的情况下使用volatile修饰符可以提高程序的性能和并发性。
当使用多线程编程时,volatile关键字会更有用。当多个线程使用同一个变量时,每个线程都会有自己的本地缓存副本。因此,当更新变量的值时,实际上是在本地缓存中更新,而不是在主变量内存中更新。另一个使用相同变量的线程不知道另一个线程更改的值。为了避免这个问题,如果将变量声明为volatile,则它不会存储在本地缓存中。每当线程更新值时,它会更新到主内存中。因此,其他线程可以访问更新后的值。
声明变量为volatile意味着:
- 不会维护缓存,即所有更改都在主内存中进行。
- 访问该变量的行为就像在同步块中一样,即使它在同步单元中。
示例 -
public class Snippet implements Runnable{ volatile int num =0; public void run(){ Thread t = Thread.currentThread(); String name = t.getName(); if(name.equals("Thread1")){ num=10; } else{ System.out.println("value of num is :"+num); } } public static void main(String args[]) throws InterruptedException{ Runnable r = new Snippet(); Thread t1 = new Thread(r); t1.setName("Thread1"); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(r); t2.setName("Thread2"); t2.start(); } }