这段并发代码速度较慢是否是因为开销造成的?还是有其他因素在起作用?

huangapple go评论79阅读模式
英文:

Is this concurrent code slower because of overhead? or is something else at play?

问题

我在尝试构建一个ArrayList类,通过在所有方法上都添加synchronized关键字的方式来使其线程安全,但这种方法非常笨拙。

import java.util.stream.*;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LongArrayListUnsafe {
    public static void main(String[] args) {
        LongArrayList dal1 = LongArrayList.withElements();

        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        for (int i = 0; i < 1000; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    for (int i = 0; i < 10; i++)
                        dal1.add(i);
                }
            });
        }
        System.out.println("Using toString(): " + dal1);

        for (int i = 0; i < dal1.size(); i++)
            System.out.println(dal1.get(i));

        System.out.println(dal1.size());
    }
}

class LongArrayList {

    private long[] items;
    private int size;

    public LongArrayList() {
        reset();
    }

    synchronized public static LongArrayList withElements(long... initialValues) {
        LongArrayList list = new LongArrayList();
        for (long l : initialValues)
            list.add(l);
        return list;
    }

    synchronized public void reset() {
        items = new long[2];
        size = 0;
    }

    synchronized public int size() {
        return size;
    }

    synchronized public long get(int i) {
        if (0 <= i && i < size)
            return items[i];
        else
            throw new IndexOutOfBoundsException(String.valueOf(i));
    }

    synchronized public long set(int i, long x) {
        if (0 <= i && i < size) {
            long old = items[i];
            items[i] = x;
            return old;
        } else
            throw new IndexOutOfBoundsException(String.valueOf(i));
    }

    synchronized public LongArrayList add(long x) {
        if (size == items.length) {
            long[] newItems = new long[items.length * 2];
            for (int i = 0; i < items.length; i++)
                newItems[i] = items[i];
            items = newItems;
        }
        items[size] = x;
        size++;
        return this;
    }

    synchronized public String toString() {
        return Arrays.stream(items, 0, size)
                .mapToObj(Long::toString)
                .collect(Collectors.joining(", ", "[", "]"));
    }
}

我正在尝试将一堆元素添加到列表中,使用一些任务。问题在于,当我增加传递给FixedThreadPool的线程数量时,我的代码运行时间与仅传递一个线程时的运行时间相同,甚至可能更慢。我有三个关于这个现象的理论:

  • 可能是因为线程开销,我创建的任务可能太小,需要让它们变得更大,以便在使用更多线程时才能获得性能提升。
  • 可能与锁竞争有关,因为我的类实现线程安全的方式非常笨拙,线程在竞争锁,从而导致整体速度变慢。
  • 我在使用线程池时可能犯了明显的错误。
英文:

I'm playing around with trying to build a arraylist class that is made threadsafe in a very clumsy way by just slapping on the synchronized keyword on all methods

import java.util.stream.*;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LongArrayListUnsafe {
public static void main(String[] args) {
LongArrayList dal1 = LongArrayList.withElements();
ExecutorService executorService =  Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (int i=0; i&lt;1000; i++) {
executorService.execute(new Runnable() {
public void run() {
for (int i=0; i&lt;10; i++)
dal1.add(i);
}
});}
System.out.println(&quot;Using toString(): &quot; + dal1);
for (int i=0; i&lt;dal1.size(); i++)
System.out.println(dal1.get(i));
System.out.println(dal1.size());} }
class LongArrayList {
private long[] items;
private int size;
public LongArrayList() {
reset();
}
synchronized public static LongArrayList withElements(long... initialValues){
LongArrayList list = new LongArrayList();
for (long l : initialValues) list.add( l );
return list;
}
// reset me to initial 
synchronized public void reset(){
items = new long[2];
size = 0;
}
// Number of items in the double list
synchronized public int size() {
return size;
}
// Return item number i
synchronized public long get(int i) {
if (0 &lt;= i &amp;&amp; i &lt; size) 
return items[i];
else 
throw new IndexOutOfBoundsException(String.valueOf(i));
}
// Replace item number i, if any, with x
synchronized public long set(int i, long x) {
if (0 &lt;= i &amp;&amp; i &lt; size) {
long old = items[i];
items[i] = x;
return old;
} else 
throw new IndexOutOfBoundsException(String.valueOf(i));}
// Add item x to end of list
synchronized public LongArrayList add(long x) {
if (size == items.length) {
long[] newItems = new long[items.length * 2];
for (int i=0; i&lt;items.length; i++)
newItems[i] = items[i];
items = newItems;
}
items[size] = x;
size++;
return this;
}
synchronized public String toString() {
return Arrays.stream(items, 0,size)
.mapToObj( Long::toString )
.collect(Collectors.joining(&quot;, &quot;, &quot;[&quot;, &quot;]&quot;));
}
}

The relevant thing I'm doing is adding a bunch elements to a list, with some tasks. The issue is that when I increase the amount of threads that I pass to the fixedthreadPool, my code runs in the same time as when I only pass only one thread, maybe even slower.
I have three theories on why this is:

  • This is because of thread overhead, and the tasks I am creating are simply too small, I need to make them bigger before it pays off to use more threads.
  • It has to do with lock contention, because my class is so clumsily threadsafe, the threads a are competing for the locks, and somehow slowing down everything
  • I'm making a completely obvious mistake in using the threadexecutorpool

答案1

得分: 0

不仅仅是因为你的任务太简单。关键问题在于你标记了add函数是同步的,这意味着只允许一个线程进入此函数。无论你使用多少执行者,在任何单一时间点,只有一个线程执行此函数,而其他线程必须等待。即使你使任务更复杂,也不会改变这一点。你需要一个更复杂的任务和更精细的同步。

至于锁竞争。是的,参见上文,当然,获取和释放锁会消耗时间。

回答评论中的问题:

  • synchronized在你调用类上同步,(即dal1,它被所有线程共享)。
  • 是的,争用是相当明显的。你自己说过"随便加"。尽管如此,对于你目前的代码,我会称其为适当的。操作中最耗时的是调整大小和复制数组,在此期间,肯定不希望其他线程修改你的数组。
英文:

It is not only that your task is to simple. The key issue is that you marked the add function is synchronized which means that only a single thread is allowed to enter this function. No matter how many executers you use, at any single point in time, there is only one thread executing this function, while the others have to wait. Even if you make the task more complex, it won't change. You need to have a more complex task and a more fine grained synchronization thereof.

As for lock contention. Yes, see above, and of course acquiring and releasing locks costs time.

To answer the question in the comments:

  • sychronized synchronizes on the object on which you invoke the class, (i.e., dal1 which is shared by all your threads).
  • yes, the contention is fairly obvious. You said yourself "just slapping". Nevertheless for the code you have I would call it adequate. The operation that takes the longest is resizing and copying of the array and during that time you certainly do not want any other thread to modify your array.

huangapple
  • 本文由 发表于 2020年10月16日 03:33:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/64378527.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定