1、锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
在Java中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:
1、如用synchronized关键字,或者使用锁对象.
2、使用volatile关键字 用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道.
volatile 修饰的变量,当有一下线程修改volatile修饰的变量时,当其它线程中的该变量的值也会修改。下面这个例子表明,在多线程的情况下,如果使用volatile修饰变量,可能造成结果错误.
volatile 的可见性,就是在java的工作内存中,被volatile修饰的变量值被修改,自动执行 store write 同步更新主内存,所以其它线程可以得到相同的值。而普通的变量,不能马上同步主内存。
扩展一下,synchronized和final修饰的变量也可是达到可见性。
package memoryModel;
/**
* volatile变量自增运算测试
*
*/
public class VolatileTest {
public static volatile int race = 0;
public static void increase() {
race++;
}
private static final int THREADS_COUNT = 20;
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
}
});
threads[i].start();
}
// 等待所有累加线程都结束
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(race);
}
}
一共创建二个线程,每个线程都将race++,如果正确的结果应该是20000,但是这个程序的每一次结果都小于20000,如果将
public static synchronized void increase() {
race++;
}
使用互斥,才能得到正确的结果。
总结,在以下1种情况下可以考虑使用volatile修饰变量
1、如果有多个线程,但是只有一个线程改变volatile修饰的值,其它的线程只能读取,则不会出现不一致的问题
其它情况使用原子操作(synchronized和java.util.concurrent)即保证原子性,也保证可见性;
最后说一下volatile与synchronized区别
- volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.