在进行多线程处理时,我一直在研究线程安全性。我正在研究使用锁来使自定义数据结构线程安全。
这是使这个自定义直方图线程安全的最合适的实现吗?
我也是新来的。如果我想帮助跟踪代码以找出它的作用,是否有可以使用的标签?
直方图类(不安全)
public class Histogram
{
protected long[] bins;
protected int min, max, range;
protected int numBins;
public Histogram(int max, int min, int numBins)
{
this.max = max;
this.min = min;
this.numBins = numBins;
bins = new long[numBins];
range = max - min + 1;
}
public void add(int num)
{
int bin = (int) Math.floor(((num - min) * 1.0 / range) * numBins);
bins[bin]++;
}
public int absDifference(Histogram histogram)
{
int sum = 0;
if (histogram.min == min && histogram.max == max && histogram.numBins == numBins)
for (int i = 0; i < bins.length; i++)
sum += (int) Math.abs(bins[i] - histogram.bins[i]);
return sum;
}
@Override
public String toString()
{
String out = String.format("{Min: %d, Max: %d, # Bins: %d, Values: ", min, max, numBins);
for (int i = 0; i < bins.length; i++)
out += bins[i] + ", ";
out = out.substring(0, out.length() - 2);
out += "}";
return out;
}
}
线程安全直方图类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HistogramSafe extends Histogram
{
private Lock[] binLocks;
public HistogramSafe(int max, int min, int numBins)
{
super(max, min, numBins);
binLocks = new ReentrantLock[numBins];
for (int i = 0; i < numBins; i++)
binLocks[i] = new ReentrantLock();
}
@Override
public void add(int num)
{
int bin = (int) Math.floor(((num - min) * 1.0 / range) * numBins);
binLocks[bin].lock();
bins[bin]++;
binLocks[bin].unlock();
}
}
答案 0 :(得分:1)
这取决于。如果您的numBins
(也min
和max
)变量无法更改,即您的数据结构为固定大小,则它应该是线程安全的,允许并行修改不同的bin。
但如果numBins
(或min
,max
)可修改,那么它就不再是线程安全的了。正如您之前访问的numBins
那样,它也是一个共享资源,并且不在同一个锁中。
线程可能会进入方法,读取numBins
然后休眠(由于线程调度程序)。现在另一个线程来执行完整方法。旧帖子会继续并设置bins[bin]++
,但numBins
的过期值。
例如,如果您还提供remove
函数,那么这甚至可能导致IndexOutOfBoundException
,因为第一个线程可能会读取10
的大小,然后其他线程会减少大小为5
。当第一个帖子继续时,它可能会尝试写入无效索引。
答案 1 :(得分:1)
为了确保方法是线程安全的,synchronized
关键字可以提供帮助。此外,任何不可变的数据结构都是主要是线程安全的。
public synchronized void methodName(){}
如Zabuza所说,这将阻止所有尝试调用该方法的线程
另一种确保线程安全的方法是制作一个同步块,它将把你想要锁定的对象作为参数
public void methodName(){
synchronized(object) {
...
}
}
答案 2 :(得分:1)
您应该调查您的bin成员使用AtomicInteger。在您的示例中,线程安全问题是由于整数(读取,添加,写入)的增量。 AtomicInteger操作是线程安全的,速度更快。
同步和锁定更适合保护复杂的数据结构。
答案 3 :(得分:0)
我将从不同的角度来看待这个问题 - 不,我认为它不是线程安全的。为什么?因为你允许继承。当有人扩展这个类时,很多事情都会出错 - 主要是覆盖add
方法。