当前位置:   article > 正文

Java 多线程三之——volatile_volatile while循环里写方法 不写方法

volatile while循环里写方法 不写方法

1、volatile的内存可见性

volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性

  1. public class RunThread extends Thread{
  2. private boolean isRunning = true;
  3. int m;
  4. public boolean isRunning() {
  5. return isRunning;
  6. }
  7. public void setRunning(boolean isRunning) {
  8. this.isRunning = isRunning;
  9. }
  10. @Override
  11. public void run() {
  12. System.out.println("进入run了");
  13. while (isRunning == true) {
  14. int a=2;
  15. int b=3;
  16. int c=a+b;
  17. m=c;
  18. }
  19. System.out.println(m);
  20. System.out.println("线程被停止了!");
  21. }
  22. }
  23. public class Run {
  24. public static void main(String[] args) throws InterruptedException {
  25. RunThread thread = new RunThread();
  26. thread.start();
  27. Thread.sleep(1000);
  28. thread.setRunning(false);
  29. System.out.println("已经赋值为false");
  30. }
  31. }

上述代码运行时会出现死循环,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循环可以被终止。

2、volatile的原子性相关问题

(1)例子

  1. import java.util.concurrent.CountDownLatch;
  2. public class Counter {
  3. public static volatile int num = 0;
  4. //使用CountDownLatch来等待计算线程执行完
  5. static CountDownLatch countDownLatch = new CountDownLatch(30);
  6. public static void main(String []args) throws InterruptedException {
  7. //开启30个线程进行累加操作
  8. for(int i=0;i<30;i++){
  9. new Thread(){
  10. public void run(){
  11. for(int j=0;j<10000;j++){
  12. num++;//自加操作
  13. }
  14. countDownLatch.countDown();
  15. }
  16. }.start();
  17. }
  18. //等待计算线程执行完
  19. countDownLatch.await();
  20. System.out.println(num);
  21. }
  22. }

上述代码最终的结果可能不是300000

问题原因因为num++不是个原子性的操作,而是个复合操作,需要三步,读取、加一、赋值,所以,在多线程环境下,有可能线程A将num读取到本地内存中,此时其他线程可能已经将num增大了很多,线程A依然对过期的num进行自加,重新写到主存中,最终导致了num的结果不合预期,而是小于30000。

(2)、解决num++操作的原子性问题

针对num++这类复合类的操作,可以使用java并发包中的原子操作类原子操作类是通过循环CAS的方式来保证其原子性的。

  1. public class Counter {
  2.   //使用原子操作类
  3. public static AtomicInteger num = new AtomicInteger(0);
  4. //使用CountDownLatch来等待计算线程执行完
  5. static CountDownLatch countDownLatch = new CountDownLatch(30);
  6. public static void main(String []args) throws InterruptedException {
  7. //开启30个线程进行累加操作
  8. for(int i=0;i<30;i++){
  9. new Thread(){
  10. public void run(){
  11. for(int j=0;j<10000;j++){
  12. num.incrementAndGet();//原子性的num++,通过循环CAS方式
  13. }
  14. countDownLatch.countDown();
  15. }
  16. }.start();
  17. }
  18. //等待计算线程执行完
  19. countDownLatch.await();
  20. System.out.println(num);
  21. }
  22. }

(3)、不能保证原子性的例子

  1. public class MyThread extends Thread {
  2. volatile public static int count;
  3. private static void addCount() {
  4. for (int i = 0; i < 100; i++) {
  5. count=i;
  6. }
  7. System.out.println("count=" + count);
  8. }
  9. @Override
  10. public void run() {
  11. addCount();
  12. }
  13. }
  14. public class Run {
  15. public static void main(String[] args) {
  16. MyThread[] mythreadArray = new MyThread[100];
  17. for (int i = 0; i < 100; i++) {
  18. mythreadArray[i] = new MyThread();
  19. }
  20. for (int i = 0; i < 100; i++) {
  21. mythreadArray[i].start();
  22. }
  23. }
  24. }

上面的“count=i;”是一个原子操作,但是运行结果大部分都是正确结果99,但是也有部分不是99的结果。

解决办法:

使用synchronized关键字加锁。(这只是一种方法,Lock和AtomicInteger原子类都可以,因为之前学过synchronized关键字,所以我们使用synchronized关键字的方法)

  1. public class MyThread extends Thread {
  2. public static int count;
  3. synchronized private static void addCount() {
  4. for (int i = 0; i < 100; i++) {
  5. count=i;
  6. }
  7. System.out.println("count=" + count);
  8. }
  9. @Override
  10. public void run() {
  11. addCount();
  12. }
  13. }

3、synchronized关键字和volatile关键字比较

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
  • volatile关键字只能用于变量,而synchronized关键字可以修饰方法以及代码块。
  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
  • volatile关键字用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/44915
推荐阅读
相关标签
  

闽ICP备14008679号