volatile关键字

volatile概念:volatile关键字的主要作用是使变量在多个线程间可见。

看一段demo:

public class RunThread extends Thread {
private boolean isRunning = true;

public void setRunning(boolean running) {
this.isRunning = running;
}

public void run() {
System.out.println("进入run方法..");
while (isRunning == true) {

}
System.out.println("线程停止");
}

public static void main(String[] args) throws InterruptedException {
RunThread rt = new RunThread();
rt.start();
Thread.sleep(3000);
rt.setRunning(false);
System.out.println("isRunning的值已经被设置了false");
Thread.sleep(1000);
System.out.println(rt.isRunning);
}
}

运行的结果会不会打印“线程结束”?
结果是不会显示,在rt运行时,由于用到了主内存的isRuning = true,此时,rt线程会把这个变量的值,拷贝到线程执行依赖的独立内存数据,
即使住内存的isRuning = true被修改为false,这是因为jdk做的优化,每个单独的线程为了效率,都是读取自己的内存空间,不去读取主内存
空间的数据。

要想实现rt线程去读取主内存空间的数据,只需要在变量前面增加volatile进行修饰,
private volatile boolean isRuning = true;
这样修改后的效果就变成了,

进入run方法..
isRunning的值已经被设置了false
线程停止
false

我们看一个内存分析图:

 

就是上面框里面写的,被volatile修饰的变量,当变量改变时,会强制线程执行引擎去主内存里去读取。

但是,这个变量只是在每个线程中具有可见性,不具备原子性。

示例总结:

  在java中,每一个线程都会有一块空座内存区,其中存在着所有线程共享的主内存中的变量值的拷贝,当线程执行时,他在自己的工作内存区中操作这些变量,为了存取一个共享的变量,一个线程通常先获取锁定并去清除它的内存工作区, 把这些共享变量从所有线程的共享内存区中正确的装入到他自己所在的工作内存区中,当线程解锁时保证该工作内存区中变量的值写回到共享内存中变量的值写回到共享内存中。

  一个线程可以执行的操作有使用(use)、赋值(assign)、装载(load)、存储(store)、锁定(lock)、解锁(unlock).

  而主内存可以执行的操作有读(read)、写(write)、锁定(lock)、解锁(unlock)、每个操作都是原子的。

  volatile的作用就是强制线程到主内存(共享内存)里去读取变量,而不去线程工作内存区里去读取,从而实现了多个线程间的变量可见,也就是满足线程安全的可见性。

 volatile关键字虽然拥有多个线程之间的可见性,但是却不具备同步性(也就是原子性),可以算上是一个轻量级的sync

,性能要比sync强很多,不会造成阻塞(在很多开源的架构里,比如netty的底层代码就大量使用volatile,可见netty性能一定是非常不错的。)这里需要注意:一般volatile用于只针对于多个线程可见的变量操作,并不能代替sync的同步功能,

 下面看一段不具备原子性的代码:

public class VolatileNoAtomic extends Thread {
private static volatile int count;
private static void addCount() {
for (int i = 0; i < 1000; i++) {
count++;
}
System.out.println(count);
}

public void run() {
addCount();
}

public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
arr[i] = new VolatileNoAtomic();
}

for (int i = 0; i < 10; i++) {
arr[i].start();
}
}
}
看一下这段代码:10个线程对一个static变量进行操作, count参数被volatile修饰,如果volatile关键字具有原子性,那最后答应的结果应该是1000*10=1W,
现在我们看一下运行结果,

前面的值可以不用管,只要看最后一个值,最后的结果不是1W,如果能满足原子特性,那最后一个答应结果的线程,一定是10000,

当然,我们也有方法使count参数具有原子性。使用原子类操作:

原子类是支持多线程并发的原子操作,后面的变量0代表着初始化的值;

下面的方法就是++,当然也有其他的方法,下面我们答应一下,看一下最后的结果,

无论运行多少次,最后的结果一定是10000,

示例总结:

volatile关键字只具备可见性,没有原子性。要实现原子性,建议使用atomic类的系列对象,支持原子性操作

(注意atomic类只保证本身方法原子性,并不保证多次操作的原子性)

 下面在看一个demo:

public class AtomicUse {
private static AtomicInteger count = new AtomicInteger(0);

public int multiAdd() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count.addAndGet(1);
count.addAndGet(2);
count.addAndGet(3);
count.addAndGet(4);
return count.get();
}

public static void main(String[] args) {
final AtomicUse au = new AtomicUse();
List<Thread> ts = new ArrayList<Thread>();
for (int i = 0; i < 100; i++) {
ts.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(au.multiAdd());
}
}));
}
for (Thread t : ts) {
t.start();
}
}
}

运行后的结果是:

会发现,每次打印的结果不是以10进行递增的,

这就是:atomic类只保证本身方法原子性,并不保证多次操作的原子性,在遇到这种情况,我们需要使用sync对
multiAdd()方法进行修饰,确保整体的原子性,那样答应的结果就是会以10递增。







原文地址:https://www.cnblogs.com/shmilyToHu/p/6393759.html