java中的volatile用法及注意事项
在应用多线程的时候,我们经常把volitile关键字和synchronized关键字比较。
synchronized是多线程中的锁机制,利用synchronized关键字,同一时间只允许一个线程访问共享资源(读写)
volidate也经常用于多线程中,注意volitile并不能保证数据更新同步,一个变量声明为volitile,
只能保证数据的可见性,一个线程在修改变量的时候,另一个线程能准确读到变量。
但是不能保证原子性操作。多个线程同时写这个变量的时候会出错。
在一定情况下使用volitile能提高效率,因为使用volitile关键字声明后,访问这个变量不需要切换线程,
比如多个线程中只有一个线程修改变量,其他线程读这个读变量,这时候用volitile声明关键字会提高效率。
一般volitile不适合的场景:
1、不能做线程安全计数器
2、不能用来修饰多个线程可修改的变量
为了确保共享变量能被准确和一致地更新 ,线程应该确保通过排他锁(synchronized)单独的获取这个变量或者通过CAS
原子操作
简述下线程的排他锁过程:
1、线程通过调用cpu修改值之后,会将缓存行中的数据写回到内存中去。在执行写回操作时,处理器可以独占任何
共享空间,而且最新的处理器,lock#信号不会锁总线,而是锁缓存,锁住总线的话,占用的资源太大。
2、写回操作会使在其他cpu里缓存了该内存地址的数据无效。例如在Pentium和P6 faminly处理器中,如果通过嗅探
一个处理器来检测其他处理器打算写的内存地址,而这个地址处于共享状态,那么正在嗅探的处理器将会使其自身的
缓存行无效,下次访问相同的内存地址时,强制执行缓存行填充。
这一部分也是由JMM(java内存模型控制的)。
此部分参考博客: volatile底层实现原理和其应用
当一个声明为volatile变量时,读写是可以控制线程对共享变量的读写互斥,即get,set方法操作共享变量是互斥的。
满足原子性的特点。但是volidate变量的++操作不满足原子性:
VolatileNode.java:
package LaboratoryReport;
public class VolatileNode {
int vl;
public VolatileNode(int i) {
this.vl=i;
// TODO Auto-generated constructor stub
}
public synchronized void set(int vl) {
this.vl=vl;
}
public synchronized int get() {
return this.vl;
}
public void add() {
int temp=get();
temp+=1l;
set(temp);
}
}
TestVolatile.java:
package LaboratoryReport;
public class TestVolatile extends Thread{
private VolatileNode a;
private String name;
public TestVolatile(VolatileNode a2,String s) {
this.a=a2;
this.name=s;
}
public void run() {
if(this.name=="线程A") {
for(int i=0;i<10000;i++) {
this.a.set(i);
}
}
else
{
for(int i=10000;i<20000;i++)
this.a.set(i);
}
}
public static void main(String[] args) throws InterruptedException {
VolatileNode a=new VolatileNode(1);
TestVolatile aTestThread=new TestVolatile(a,"线程A");
TestVolatile bTestThread=new TestVolatile(a,"线程B");
aTestThread.start();
bTestThread.start();
Thread.sleep(1000);
System.out.println(a.get());
}
}
package LaboratoryReport;
public class TestVolatile extends Thread{
private VolatileNode a;
private String name;
public TestVolatile(VolatileNode a2,String s) {
this.a=a2;
this.name=s;
}
public void run() {
for(int i=0;i<10000;i++) {
this.a.add();
}
}
public static void main(String[] args) throws InterruptedException {
VolatileNode a=new VolatileNode(1);
TestVolatile aTestThread=new TestVolatile(a,"============");
TestVolatile bTestThread=new TestVolatile(a,"yyyyyyyyyyyy");
aTestThread.start();
bTestThread.start();
Thread.sleep(1000);
System.out.println(a.get());
}
}
最后这个共享变量没有加到20000,所以用volitile共享变量的时候注意共享变量的使用,因为涉及到多个线程++的时候
是不安全的,因为++并不直接改变共享变量的值,而需要更多的方式达到++操作。有兴趣的小伙伴可以把上面实现的++
操作直接换成++,也是不能保证线程同步的。(笔者亲试)
解决策略:
1:用类实现runnable接口代替继承thread。
2:用线程锁synchronized来实现线程同步
3:线程启动用run方法。