赞
踩
volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性。
- public class RunThread extends Thread{
- private boolean isRunning = true;
- int m;
- public boolean isRunning() {
- return isRunning;
- }
- public void setRunning(boolean isRunning) {
- this.isRunning = isRunning;
- }
- @Override
- public void run() {
- System.out.println("进入run了");
- while (isRunning == true) {
- int a=2;
- int b=3;
- int c=a+b;
- m=c;
- }
- System.out.println(m);
- System.out.println("线程被停止了!");
- }
- }
- public class Run {
- public static void main(String[] args) throws InterruptedException {
- RunThread thread = new RunThread();
- thread.start();
- Thread.sleep(1000);
- thread.setRunning(false);
- System.out.println("已经赋值为false");
- }
- }
上述代码运行时会出现死循环,RunThread类中的isRunning变量没有加上volatile关键字时,运行以上代码会出现死循环,这是因为isRunning变量虽然被修改但是没有被写到主存中,这也就导致该线程在本地内存中的值一直为true,这样就导致了死循环的产生。
解决办法:isRunning变量前加上volatile关键字即可。这样运行就不会出现死循环了。
注意:
假如把while循环代码里加上任意一个输出语句或者sleep方法你会发现死循环也会停止,不管isRunning变量是否被加上了上volatile关键字。
原因:
JVM会尽力保证内存的可见性,即便这个变量没有加同步关键字。换句话说,只要CPU有时间,JVM会尽力去保证变量值的更新。这种与volatile关键字的不同在于,volatile关键字会强制的保证线程的可见性。而不加这个关键字,JVM也会尽力去保证可见性,但是如果CPU一直有其他的事情在处理,它也没办法。最开始的代码,一直处于死循环中,CPU处于一直占用的状态,这个时候CPU没有时间,JVM也不能强制要求CPU分点时间去取最新的变量值。而加了输出或者sleep语句之后,CPU就有可能有时间去保证内存的可见性,于是while循环可以被终止。
(1)例子
- import java.util.concurrent.CountDownLatch;
- public class Counter {
- public static volatile int num = 0;
- //使用CountDownLatch来等待计算线程执行完
- static CountDownLatch countDownLatch = new CountDownLatch(30);
- public static void main(String []args) throws InterruptedException {
- //开启30个线程进行累加操作
- for(int i=0;i<30;i++){
- new Thread(){
- public void run(){
- for(int j=0;j<10000;j++){
- num++;//自加操作
- }
- countDownLatch.countDown();
- }
- }.start();
- }
- //等待计算线程执行完
- countDownLatch.await();
- System.out.println(num);
- }
- }
上述代码最终的结果可能不是300000
问题原因:因为num++不是个原子性的操作,而是个复合操作,需要三步,读取、加一、赋值,所以,在多线程环境下,有可能线程A将num读取到本地内存中,此时其他线程可能已经将num增大了很多,线程A依然对过期的num进行自加,重新写到主存中,最终导致了num的结果不合预期,而是小于30000。
(2)、解决num++操作的原子性问题
针对num++这类复合类的操作,可以使用java并发包中的原子操作类原子操作类是通过循环CAS的方式来保证其原子性的。
- public class Counter {
- //使用原子操作类
- public static AtomicInteger num = new AtomicInteger(0);
- //使用CountDownLatch来等待计算线程执行完
- static CountDownLatch countDownLatch = new CountDownLatch(30);
- public static void main(String []args) throws InterruptedException {
- //开启30个线程进行累加操作
- for(int i=0;i<30;i++){
- new Thread(){
- public void run(){
- for(int j=0;j<10000;j++){
- num.incrementAndGet();//原子性的num++,通过循环CAS方式
- }
- countDownLatch.countDown();
- }
- }.start();
- }
- //等待计算线程执行完
- countDownLatch.await();
- System.out.println(num);
- }
- }
(3)、不能保证原子性的例子
- public class MyThread extends Thread {
- volatile public static int count;
- private static void addCount() {
- for (int i = 0; i < 100; i++) {
- count=i;
- }
- System.out.println("count=" + count);
- }
- @Override
- public void run() {
- addCount();
- }
- }
- public class Run {
- public static void main(String[] args) {
- MyThread[] mythreadArray = new MyThread[100];
- for (int i = 0; i < 100; i++) {
- mythreadArray[i] = new MyThread();
- }
- for (int i = 0; i < 100; i++) {
- mythreadArray[i].start();
- }
- }
- }
上面的“count=i;”是一个原子操作,但是运行结果大部分都是正确结果99,但是也有部分不是99的结果。
解决办法:
使用synchronized关键字加锁。(这只是一种方法,Lock和AtomicInteger原子类都可以,因为之前学过synchronized关键字,所以我们使用synchronized关键字的方法)
- public class MyThread extends Thread {
- public static int count;
- synchronized private static void addCount() {
- for (int i = 0; i < 100; i++) {
- count=i;
- }
- System.out.println("count=" + count);
- }
- @Override
- public void run() {
- addCount();
- }
- }
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。