【问题标题】:Threads with shared integer object not working as expected具有共享整数对象的线程未按预期工作
【发布时间】:2019-07-12 04:56:20
【问题描述】:

我有一个问题,我必须以这种格式打印数字。

First  1
First  2
Second  3
Second  4
First  5
First  6
Second  7
Second  8
First  9
and so on...

我已经实现了我的可运行接口,如下所示。

class ThreadDemo implements Runnable {

 public volatile Integer num;

 public Object lock;

 public ThreadDemo(Integer num, Object lock) {
  this.num = num;
  this.lock = lock;
 }

 @Override
 public void run() {

  try {
   while (true) {
    int count = 0;
    synchronized(lock) {
     Thread.sleep(100);
     while (count < 2) {
      System.out.println(Thread.currentThread().getName() + "  " + num++);
      count++;

     }
     lock.notify();
     lock.wait();
    }
   }
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
}

我的主要课程如下

public class CoWorkingThreads {
 private static volatile Integer num = new Integer(1);
 public static void main(String...args) {
  Object lock = new Object();
  Thread thread1 = new Thread(new ThreadDemo(num, lock), "First");
  thread1.start();
  Thread thread2 = new Thread(new ThreadDemo(num, lock), "Second");
  thread2.start();

 }
}

当我运行程序时,我得到如下输出

First  1
First  2
Second  1
Second  2
First  3
First  4
Second  3
Second  4

而不是之前预期的结果。但是当我将整数更改为原子整数类型时,我开始得到预期的结果。谁能解释我可以做些什么来使它使用整数而不是使用原子整数运行

【问题讨论】:

    标签: java multithreading java-threads atomicinteger


    【解决方案1】:

    Java 整数不能通过引用传递。在您的代码中,每个线程都会创建变量的副本。但是 atomicInteger 可以通过引用传递。

    另外,为了得到正确的结果,您可以将 num 变量更改为静态变量。

    public static Integer num = 1;
    
    public Object lock;
    public ThreadDemo(Integer num, Object lock) {
        //this.num = num;
        this.lock =lock;
    }
    

    【讨论】:

      【解决方案2】:

      您的问题是整数类是Immutable,因此您不能在单独的线程中使用它来引用共享值。答案:创建您自己的 Mutable Integer 类。

      您可以在 SO here 上找到回答的类似问题

      【讨论】:

        【解决方案3】:

        仅据您所知,在Object 上,您可能希望尝试使用Lock(s)(例如ReentrantLock)及其关联的Condition(s),而不是使用synchronized 块.

        使用Condition(s),您可以在线程之间以互斥的方式管理您的共享资源。

        【讨论】:

          【解决方案4】:

          我仍然认为这个问题没有得到正确回答。这里的缺陷是您从未将共享数据标记为static。所以每个线程都有自己独立的副本。 Integer 是一个不可变的包装类,这是真的,但在这种情况下它与此无关。让我们深入了解num++++ 运算符仅适用于(原始)整数类型。在幕后,num 被取消装箱,++ 被应用,然后将结果分配回num(在装箱转换之后)。 Integer 类没有 ++ 运算符。事实上,Integer 对象是不可变的。

          不可变意味着每次增加并创建一个新的值对象。并且该新值对象被分配回您的 num 引用。但是两个线程有​​自己的num 引用副本,指向不同的Integer 盒装原语。因此,它们彼此独立地增加它,而彼此不可见。如果你想在线程之间共享它,你必须在声明处使用static 访问修饰符。将两个值传递给共享变量是没有意义的。相反,您可以内联初始化它。这是固定版本。

          public class ThreadDemo implements Runnable {
              public static Integer num = 1;
          
              public static final Object lock = new Object();
          
              public ThreadDemo() {
              }
          
              @Override
              public void run() {
          
                  try {
                      while (true) {
                          int count = 0;
                          synchronized (lock) {
                              Thread.sleep(100);
                              while (count < 2) {
                                  System.out.println(Thread.currentThread().getName() + "  " + num++);
                                  count++;
          
                              }
                              lock.notify();
                              lock.wait();
                          }
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
          
          public class CoWorkingThreads {
              public static void main(String[] args) {
                  Thread thread1 = new Thread(new ThreadDemo(), "First");
                  thread1.start();
                  Thread thread2 = new Thread(new ThreadDemo(), "Second");
                  thread2.start();
              }
          }
          

          最终使用客户端提供的锁对象违反了同步策略的封装。所以我改用了内部私有锁对象。

          这是新的输出。

          前 1 前 2 后 3 秒 4 前 5 前 6 秒 7 第二 8 前 9 前 10

          【讨论】:

          • 执行此操作后,我错过了整数变量中的静态字段,它按预期工作。你能告诉我为什么客户端提供的锁不应该用于同步吗?如果我们有两个不同的线程而不是单线程类,还有什么替代方法。
          • 你的类应该封装同步策略。如果您要求您的客户端传入锁对象,您的客户端就会知道您正在使用该锁进行同步。这违反了对同步策略的封装。此外,您不能信任您的客户。如果在其他地方使用相同的锁,可能会发生锁争用。但是我不明白你的第二个问题。更多详情请阅读本书:amazon.com/gp/product/0321349601/…
          猜你喜欢
          • 2017-08-25
          • 1970-01-01
          • 2018-04-27
          • 1970-01-01
          • 1970-01-01
          • 2018-12-05
          • 2017-08-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多