java中的volatile关键字的功能
volatile是java中的一个类型修饰符。它是被设计用来修饰被不同线程访问和修改的变量。如果不加入volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
1,可见性
可见性指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。顺便一提,工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。
2,禁止指令重排序优化
禁止指令重排序优化。大家知道我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。编译器只保证程序执行结果与源代码相同,却不保证实际指令的顺序与源代码相同。这在单线程看起来没什么问题,然而一旦引入多线程,这种乱序就可能导致严重问题。volatile关键字就可以从语义上解决这个问题。
注意,禁止指令重排优化这条语义直到jdk1.5以后才能正确工作。此前的JDK中即使将变量声明为volatile也无法完全避免重排序所导致的问题。所以,在jdk1.5版本前,双重检查锁形式的单例模式是无法保证线程安全的。因此,下面的单例模式的代码,在JDK1.5之前是不能保证线程安全的。
public class SingleTon {
//私有的、静态的、volatile的对象
private static volatile SingleTon singleTon = null;
//私有构造方法
private SingleTon(){
}
//公共的、静态的成员方法
public static Singleton getSingleton(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
一句话概括:volatile 并不完全是线程安全的。
volatile关键字在实际中的应用
volatile关键字,作为一般的开发者,用到的机会比较少,因此很难理解volatile关键字的妙处。如果能够结合实际应用场景来学习的话,我想能够加深大家对它的理解。
ConcurrentHashMap的源代码中,volatile有一处经典的应用。
ConcurrentHashMap是同步HashMap,是一种线程安全,并且高效的HashMap。HashmMap不是线程安全的,而ConcurrentHashMap是线程安全的;Hashtable是线程安全的,
但是效率很低,ConcurrentHashMap也是线程安全的,效率高于Hashtable。
ConcurrentHashMap实现线程安全,用到了很多经典的算法和思路,volatile关键字就是其中之一。
关于ConcurrentHashMap这个类的说明,这里不再赘述,贴上源代码中的官方说明,有兴趣的朋友可以读一下。
/**
* A hash table supporting full concurrency of retrievals and
* adjustable expected concurrency for updates. This class obeys the
* same functional specification as {@link java.util.Hashtable}, and
* includes versions of methods corresponding to each method of
* Hashtable. However, even though all operations are
* thread-safe, retrieval operations do not entail locking,
* and there is not any support for locking the entire table
* in a way that prevents all access. This class is fully
* interoperable with Hashtable in programs that rely on its
* thread safety but not on its synchronization details.
*
* Retrieval operations (including get) generally do not
* block, so may overlap with update operations (including
* put and remove). Retrievals reflect the results
* of the most recently completed update operations holding
* upon their onset. For aggregate operations such as putAll
* and clear, concurrent retrievals may reflect insertion or
* removal of only some entries. Similarly, Iterators and
* Enumerations return elements reflecting the state of the hash table
* at some point at or since the creation of the iterator/enumeration.
* They do not throw {@link ConcurrentModificationException}.
* However, iterators are designed to be used by only one thread at a time.
*
*
The allowed concurrency among update operations is guided by
* the optional concurrencyLevel constructor argument
* (default 16), which is used as a hint for internal sizing. The
* table is internally partitioned to try to permit the indicated
* number of concurrent updates without contention. Because placement
* in hash tables is essentially random, the actual concurrency will
* vary. Ideally, you should choose a value to accommodate as many
* threads as will ever concurrently modify the table. Using a
* significantly higher value than you need can waste space and time,
* and a significantly lower value can lead to thread contention. But
* overestimates and underestimates within an order of magnitude do
* not usually have much noticeable impact. A value of one is
* appropriate when it is known that only one thread will modify and
* all others will only read. Also, resizing this or any other kind of
* hash table is a relatively slow operation, so, when possible, it is
* a good idea to provide estimates of expected table sizes in
* constructors.
*
*
This class and its views and iterators implement all of the
* optional methods of the {@link Map} and {@link Iterator}
* interfaces.
*
*
Like {@link Hashtable} but unlike {@link HashMap}, this class
* does not allow null to be used as a key or value.
*
*
This class is a member of the
*
* Java Collections Framework.
*
* @since 1.5
* @author Doug Lea
* @param the type of keys maintained by this map
* @param the type of mapped values
*/
/* Specialized implementations of map methods */
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).get(key, hash);
}
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key.equals(k))},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
* @throws NullPointerException if the specified key is null
* @see #put(Object, Object)
*/
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
能够在多线程之间保持可见性,多线程可以同时读,并且读到的都是最新的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖原值),在get操作里只需要读,不需要写count变量。之所以能够保证多线程读到的都是最新的值,
是因为根据Java内存模型的happen before原则,对于volatile字段的写入操作先于读操作,也就是说,
2个线程同时修改和读取volatile变量的值,写操作先执行,读操作后执行。这就是volatile的经典应用之一。
Segment中(或者说ConcurrentHashMap)count全局变量的定义如下:
/**
* The number of elements in this segment's region.
*/
transient volatile int count;
/**
* The total number of entries in the hash table.
*/
private transient int count;