实现线程安全的共享计数器的函数式方法

11 浏览
0 Comments

实现线程安全的共享计数器的函数式方法

我对Scala和函数式编程相对陌生,我喜欢使用不可变对象来避免许多线程安全陷阱的想法。然而,有一件事让我困扰,那就是用于教授线程安全的经典示例 - 共享计数器。\n我想知道是否可能使用不可变对象和函数式概念来实现一个线程安全的计数器(在这个示例中是一个请求计数器),并完全避免同步。\n因此,首先是计数器的经典可变版本(请原谅我使用公共成员变量,只是为了简洁起见)。\n可变的、非线程安全版本:\n

public class Servlet extends HttpServlet {
  public int requestCount = 0; 
  @Override
  public void service(ServletRequest req, ServletResponse res) throws ... {
    requestCount++; //线程不安全
    super.service(req, res);  
  }
}

\n可变的、经典线程安全版本:\n

public class Servlet extends HttpServlet {
  public volatile int requestCount = 0;
  @Override
  public void service(ServletRequest req, ServletResponse res) throws ... {
    synchronized (this) {
      requestCount++;
    }
    super.service(req, res);  
  }
}

\n我在想是否有办法使用不可变对象和volatile变量实现线程安全而无需同步。\n因此,这是我天真的尝试。思路是为计数器使用一个不可变对象,并通过volatile变量来替换对它的引用。虽然感觉有点靠不住,但值得一试。\nHolder类:\n

public class Incrementer {
  private final int value;
  public Incrementer(final int oldValue) {
    this.value = oldValue + 1;
  }
  public Incrementer() {
    this.value = 0;
  }
  public int getValue() {
    return value;
  }
}

\n修改后的servlet:\n

public class Servlet extends HttpServlet {
  public volatile Incrementer incrementer = new Incrementer();
  @Override
  public void service(ServletRequest req, ServletResponse res) throws ... {
    incrementer = new Incrementer(incrementer.getValue());
    super.service(req, res);
  }
}

\n我强烈感觉这个也不是线程安全的,因为我从incrementer中读取值,可能会得到一个旧的值(例如,如果引用已经被另一个线程替换)。如果确实不是线程安全的,那么我想知道是否有任何\"函数式\"的方法来处理这样的计数器场景,而不需要锁定/同步。\n因此,我的问题是:\n1. 这个代码是否线程安全?\n2. 如果是,为什么是线程安全的?\n3. 如果不是,是否有任何方法可以实现这样的计数器而无需同步?\n尽管上面的示例代码是Java代码,但也欢迎使用Scala进行回复。

0
0 Comments

问题的出现原因是在多线程环境下,对共享计数器进行操作时可能会出现线程不安全的情况。如果使用可变的方式进行操作,可能会导致一个线程修改了可变列表,从而导致另一个线程的索引操作失败。或者需要复制数据,如果没有专门设计用于重用的集合,并且向量较长时,可能会非常昂贵。或者需要在两个线程中同步大块的代码,而不仅仅是原子地获取和/或设置数据。

解决方法是使用不可变的方式来实现线程安全的共享计数器。这可以通过使用原子引用来实现。在上述代码中,通过使用AtomicReference来包装一个Vector对象来实现线程安全的共享计数器。在Thread 1中,使用x.get获取Vector对象,并打印出最后一个元素。在Thread 2中,使用x.getAndSet方法获取Vector对象,并将其尾部元素移除。

使用不可变的方式实现的好处是不需要跟踪谁拥有哪个数据结构的副本,并且不需要像疯狂一样同步所有人,因为数据可能在你的操作过程中发生变化并抛出异常。

总结起来,通过使用不可变的方式和原子引用,可以实现线程安全的共享计数器,避免了在多线程环境下可能出现的线程不安全问题。

0
0 Comments

问题:如何以功能性的方式实现一个线程安全的共享计数器?

原因:在没有在同步块中创建不可变对象的情况下,上述代码是不线程安全的。在线程竞争条件下,存在创建损坏的不可变对象的可能性。

解决方法:可以使用AtomicInteger来实现相同的功能,以避免显式同步。

public class Servlet extends HttpServlet {
  public AtomicInteger incrementer = new AtomicInteger (0);
  public void service(ServletRequest req, ServletResponse res) throws ... {
    int newValue = incrementer.incrementAndGet();
    super.service(req, res);
  }
}

有趣的是,我确信AtomicInteger在内部使用同步,所以我甚至没有考虑它作为一个答案,但是看源代码似乎它使用了其他方法(本地代码)来实现这个目标。[代码链接](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java)

0