我目前正在为N体问题实施Barnes-Hut算法的多线程版本。虽然算法有效,但它并没有得到很好的优化,我试图减少程序的运行时间。
我确保有几个线程可以准确找到我正在使用的空间的边界,并意识到我设置边框的最高级别对象中的代码相当不优化。它看起来像这样:
public synchronized void setBorders(float maxX, float minX, float maxY, float minY, int thread){
if(maxX > this.maxX){
this.maxX = maxX;
}
if(maxY > this.maxY){
this.maxY = maxY;
}
if(this.minX > minX){
this.minX = minX;
}
if(this.minY > minY){
this.minY = minY;
}
}
我有几个线程试图在他们找出各自的值后尝试访问此方法。由于同步对象只能在给定时间由单个线程访问,因此可以显着改善。
我想到的可能解决方案是删除" public synchronized void"并将代码重写为:
public synchronized void setBorders(float maxX, float minX, float maxY, float minY, int thread){
Synchronize(this){
if(maxX > this.maxX){
this.maxX = maxX;
}
}
Synchronize(this){
if(maxY > this.maxY){
this.maxY = maxY;
}
}
Synchronize(this){
if(this.minX > minX){
this.minX = minX;
}
}
Synchronize(this){
if(this.minY > minY){
this.minY = minY;
}
}
}
}
如果我对Synchronized块的理解是正确的,那么在任何给定时间只有一个线程可以访问Synchronize(this)块内的代码,这可以加快我的代码。
这会起作用,还是有理由我应该避免这种错误?
编辑:哇,我对你们给予的帮助的速度和准确性感到惊讶。我非常感谢这一切!
答案 0 :(得分:1)
该代码应该非常快速地执行 ,所以我怀疑是否将其拆分为更多同步块会减少争用。我已经同步了单一的功能级别。
但是,如果代码做得更多,例如
...
if(maxX > this.maxX){
this.maxX = maxX;
doSomeSlowerCalculation();
updateSomeComplexSharedDataStructure();
}
...
然后将其拆分为单独的同步块可以帮助
答案 1 :(得分:1)
有三种方法可以避免或减少同步:
使用Guava中的一个类,你可以做到
private final AtomicDouble maxX = new AtomicDouble(Double.MIN_VALUE);
简单地
while (true) {
double currentMaxX = this.maxX.get();
if (currentMaxX >= maxX) break;
boolean ok = compareAndSet(currentMaxX, maxX);
if (ok) break;
}
如果你真的必须使用float,编写自己的类,可以使用like these行。{/ p>
没有同步,只有CAS。
使用
private volatile float maxX;
和Java 1.5或更高版本,以下将做
if (maxX > this.maxX) {
synchronized (this) {
if (maxX > this.maxX) {
this.maxX = maxX;
}
}
}
计算您的本地最大/最小值,并且仅在几次迭代后更新共享状态。这是最简单的,但可能不适用于您的用例。
答案 2 :(得分:0)
为了更有效地优化它,你可以将你的setBorders例程分解为4个方法(每个参数1个)并使用4个锁定对象来单独锁定每个setter方法......但是这只会在你的setter被设置时更有效单独调用(并不总是作为一个块)和/或并不总是以相同的顺序调用,或者当你确定值没有实际改变时你可以快速逃离设置器
答案 3 :(得分:0)
首先,将synchronized添加到方法定义,如
synchronized void foo() {
...
}
与
相同void foo() {
synchronized(this) {
...
}
}
如果你像第二个例子一样嵌套同步块:
synchronized void foo() {
synchronized(this) {
...
}
synchronized(this) {
...
}
}
然后内部块对同步没有任何影响,调用方法的线程仍然必须在输入方法时获取对象的监视器,并在退出时释放它。
如果从方法中删除synchronized,那么只需:
void foo() {
synchronized(this) {
...
}
synchronized(this) {
...
}
}
然后当线程执行此代码时,它们必须分别获取每个块。因此,如果多个线程正在设置不同的变量,那么您可能最终得到的对象具有由一个线程设置的一些字段和由另一个线程设置的其他字段。
使用较小的同步块,线程可能会花更多时间争用锁。对于这些块中的每一个,调度程序必须决定哪个线程接下来获得锁定。锁定获取是不公平的,不确定哪个线程将获得锁定。
不太明显的是,较低粒度的方法会更快,让线程进入,设置所有字段并退出可能会更好。第二个例子可能只是让调度程序更加努力,没有充分的理由。唯一的区别是第二种方法将允许混合来自不同线程的数据。