重庆大傑 2024-06-25 11:51 采纳率: 100%
浏览 3
已结题

java并发取值的问题

遇到一个并发取值问题

① 如下定义两个线程t1和t2,都能正常执行完成。


private static Test test = new Test();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            while (true) {
                if (test.object != null) {
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("thread-1 done");
        }, "thread-1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(10);
                test.object = new Object();
                System.out.println("thread-2 done");
            }
        }, "thread02");
        t2.start();

    }

    static class Test {
        public Object object;
    }

② 如果把t1的定义改成:

Thread t1 = new Thread(() -> {
            while (true) {
                if (test.object != null) {
                    break;
                }
            }
            System.out.println("thread-1 done");
        }, "thread-1");


那t1一直死循环无法执行完成,这是为什么?这里先不考虑用volatile

  • 写回答

2条回答 默认 最新

  • 阿里嘎多学长 2024-06-25 11:51
    关注

    以下内容由CHATGPT及阿里嘎多学长共同生成、有用望采纳:


    在Java中,当多个线程访问共享变量时,如果没有适当的同步机制,就可能发生内存可见性问题。在你的例子中,test.object是一个共享变量,被两个线程t1t2访问。

    问题原因:

    1. CPU缓存:现代CPU为了提高效率,会将频繁访问的数据缓存到自己的缓存中。当t2修改了test.object的值后,这个新值可能首先被写入到CPU缓存中,而不是立即写入主内存。
    2. 内存屏障:由于没有使用volatile关键字,Java内存模型不保证t1能够立即看到t2test.object的修改。volatile关键字会引入内存屏障,确保变量的读写操作对所有线程都是可见的。
    3. 无休眠导致的问题:当t1没有Thread.sleep(10);时,它检查test.object的频率非常高,这增加了CPU缓存中旧值的持续使用,减少了t1看到新值的机会。

    解决方案:

    不使用volatile的情况下,有几种方法可以解决这个问题:

    1. 使用锁:通过synchronized块或ReentrantLock等锁机制来同步对共享变量的访问。这可以确保在t2修改test.object后,t1能够看到最新的值。

      public class Test {
          private final Object lock = new Object();
          public Object object;
      
          public void setObject(Object obj) {
              synchronized (lock) {
                  this.object = obj;
                  lock.notifyAll(); // 如果需要通知等待的线程
              }
          }
      
          public Object getObject() {
              synchronized (lock) {
                  while (this.object == null) {
                      try {
                          lock.wait(); // 等待通知
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  return this.object;
              }
          }
      }
      
    2. 使用原子类:Java提供了一些原子类,如AtomicReference,它们提供了一种无锁的方式来保证内存可见性。

      import java.util.concurrent.atomic.AtomicReference;
      
      public class Test {
          private final AtomicReference<Object> object = new AtomicReference<>(null);
      
          public void setObject(Object obj) {
              object.set(obj);
          }
      
          public Object getObject() {
              while (object.get() == null) {
                  // 这里可以添加Thread.sleep()来减少CPU使用率
              }
              return object.get();
          }
      }
      
    3. 使用Thread.yield():虽然这不是一个可靠的解决方案,但Thread.yield()可以提示线程调度器当前线程愿意放弃对CPU的使用,这可能会给其他线程机会来执行。

      while (test.object == null) {
          Thread.yield(); // 提示线程调度器当前线程愿意放弃CPU
      }
      

    参考链接:

    请注意,这些解决方案都涉及到对Java并发编程的深入理解,确保在实际应用中选择适合你场景的方案。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论 编辑记录
查看更多回答(1条)

报告相同问题?

问题事件

  • 系统已结题 7月3日
  • 已采纳回答 6月25日
  • 创建了问题 6月25日

悬赏问题

  • ¥15 readimage函数怎么读取变量图片地址
  • ¥50 网课里面实习定位打卡
  • ¥50 Delphi 非客户区窗口阴影?
  • ¥15 cv2 morphologyEx函数报错
  • ¥15 有没有知道鸿蒙OS高级开发者新题答案的
  • ¥15 有没有人能帮我一下android
  • ¥20 做一个干部信息管理系统 软件
  • ¥15 通过4G模块EC600N向阿里云物联网平台物模型上面发送字符串,现在发送int数据是成功的,发送字符串就是不成功
  • ¥15 IDA反编译,代码识别失败
  • ¥70 matlab代码修改