Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/327.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
用Java编写线程安全的模块计数器_Java_Multithreading_Concurrency_Thread Safety - Fatal编程技术网

用Java编写线程安全的模块计数器

用Java编写线程安全的模块计数器,java,multithreading,concurrency,thread-safety,Java,Multithreading,Concurrency,Thread Safety,完整免责声明:这并不是一个真正的家庭作业,但我给它贴上了这样的标签,因为它主要是一个自学练习,而不是真正的“为了工作” 假设我想用Java编写一个简单的线程安全模块计数器。也就是说,如果模M为3,则计数器应无限循环0,1,2,0,1,2,… 这里有一个尝试: import java.util.concurrent.atomic.AtomicInteger; public class AtomicModularCounter { private final AtomicInteger t

完整免责声明:这并不是一个真正的家庭作业,但我给它贴上了这样的标签,因为它主要是一个自学练习,而不是真正的“为了工作”

假设我想用Java编写一个简单的线程安全模块计数器。也就是说,如果模
M
为3,则计数器应无限循环
0,1,2,0,1,2,…

这里有一个尝试:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicModularCounter {
    private final AtomicInteger tick = new AtomicInteger();
    private final int M;

    public AtomicModularCounter(int M) {
        this.M = M;
    }
    public int next() {
        return modulo(tick.getAndIncrement(), M);
    }
    private final static int modulo(int v, int M) {
        return ((v % M) + M) % M;
    }
}
我对这段代码的分析(可能是错误的)是,因为它使用了,所以即使没有任何显式的
synchronized
方法/块,它也是线程安全的

不幸的是,“算法”本身不太“有效”,因为当
勾选
环绕
整数.MAX_值时,
next()
可能返回错误的值,具体取决于模
M
。即:

System.out.println(Integer.MAX_VALUE + 1 == Integer.MIN_VALUE); // true
System.out.println(modulo(Integer.MAX_VALUE, 3)); // 1
System.out.println(modulo(Integer.MIN_VALUE, 3)); // 1
也就是说,当模为3且
勾选
换行时,对
next()
的两个调用将返回
1,1

next()
获取无序值也可能存在问题,例如:

  • Thread1调用
    next()
  • Thread2调用
    next()
  • Thread2完成勾选.getAndIncrement()
  • ,返回x
  • Thread1完成勾选.getAndIncrement()
  • ,返回y=x+1(mod M) 这里,除了前面提到的包装问题,x和y确实是这两个
    next()
    调用返回的两个正确值,但是根据计数器行为的指定方式,可以认为它们是无序的。也就是说,我们现在有了(Thread1,y)和(Thread2,x),但也许真的应该指定(Thread1,x)和(Thread2,y)是“正确的”行为

    所以,根据一些单词的定义,
    atomicmodulearcounter
    是线程安全的,但实际上不是原子的

    因此,问题是:

    • 我的分析正确吗?如果没有,请指出任何错误
    • 我上面的最后一句话是否使用了正确的术语?如果不是,正确的说法是什么
    • 如果上述问题是真实的,那么您将如何解决
    • 通过利用
      AtomicInteger
      的原子性,您能否在不使用
      synchronized
      的情况下修复它
    • 您如何编写它,使
      勾号
      本身的范围由模控制,甚至没有机会覆盖
      整数.MAX_值
      • 我们可以假设
        M
        至少比
        整数小一个数量级。如有必要,最大值

    附录 这里有一个类似于无序“问题”的
    列表

    • Thread1调用添加(第一个)
  • Thread2调用
    add(秒)
  • 现在,如果我们成功地更新了列表,添加了两个元素,但是
    second
    first
    之前,也就是在末尾,那么这是“线程安全”吗

    如果这是“线程安全”,那么它不是什么?也就是说,如果我们指定在上述场景中,
    first
    应始终位于
    second
    之前,那么该并发属性的名称是什么?(我称之为“原子性”,但我不确定这是否是正确的术语)


    值得一提的是,与此无序方面相关的
    Collections.synchronizedList
    行为是什么?

    我想说的是,除了包装之外,这很好。当两个方法调用有效地同时进行时,您无法保证哪一个将首先发生

    代码仍然是原子的,因为无论哪一个先发生,它们都不会相互干扰

    基本上,如果您的代码试图依赖于同时调用的顺序,那么您已经有了竞争条件。即使在调用代码中,一个线程在另一个线程之前到达
    next()
    调用的开始,您也可以想象它在进入
    next()
    调用之前到达其时间片的末尾,从而允许第二个线程进入

    如果
    next()
    调用有任何其他副作用-例如,它打印出“从线程开始(线程id)”,然后返回下一个值,那么它就不是原子的;你的行为会有明显的不同。事实上,我认为你很好

    关于包装,需要考虑的一件事是:如果使用
    AtomicLong
    :),在包装之前,可以让计数器持续更长的时间

    编辑:我刚刚想到了一种在所有现实场景中避免包装问题的简洁方法:

    • 定义一些大的数字M*100000(或其他数字)。这应该被选择为足够大,不会被频繁点击(因为这会降低性能),但足够小,您可以期望下面的“修复”循环在太多线程添加到勾号以导致其缠绕之前有效
    • 使用
      getAndIncrement()
      获取值时,请检查该值是否大于此数字。如果是的话,进入一个“还原循环”,它看起来像这样:

      long tmp;
      while ((tmp = tick.get()) > SAFETY_VALUE))
      {
          long newValue = tmp - SAFETY_VALUE;
          tick.compareAndSet(tmp, newValue);
      }
      
    基本上这是说,“我们需要通过减少一些模的倍数将值恢复到一个安全范围”(这样它就不会改变mod M的值)。它在一个紧密的循环中完成这项工作,基本上计算出新值应该是什么,但只有在没有其他东西改变值的情况下才进行更改


    在病理条件下,如果有无限多的线程试图增加值,这可能会导致问题,但我认为这实际上是可以解决的。

    关于原子性问题:我认为计数器本身不可能提供行为来保证你所暗示的语义

    我想我们有一根线在做一些工作

      A - get some stuff (for example receive a message)
      B - prepare to call Counter
      C - Enter Counter <=== counter code is now in control
      D - Increment
      E - return from Counter <==== just about to leave counter's control
      F - application continues
    
    如果实现在每种情况下都有明确定义的行为,那么它是线程安全的。有趣的问题是什么行为是方便的

    我们可以简单地在列表上同步,从而实现ea
    pre-A - Get a lock on Counter (or other lock)
      A - get some stuff (for example receive a message)
      B - prepare to call Counter
      C - Enter Counter <=== counter code is now in control
      D - Increment
      E - return from Counter <==== just about to leave counter's control
      F - application continues
    post- F - release lock
    
    a). Two threads attempt to add
    b). One thread adds item with key "X", another attempts to delete the item with key "X"
    C). One thread is iterating while a second thread is adding
    
    int hash = 0;
    
    int hashCode(){
        int hash = this.hash;
        if(hash==0){
            hash = this.hash = calcHash();
        }
        return hash;
     }
    
    public final int getAndIncrement(int modulo) {
        for (;;) {
            int current = atomicInteger.get();
            int next = (current + 1) % modulo;
            if (atomicInteger.compareAndSet(current, next))
                return current;
        }
    }