哪种方法会更好 - 多线程示例

时间:2014-08-27 18:41:14

标签: java multithreading performance concurrency memory-efficient

我想知道在Java 7中使用多线程执行任务的这两种方法之间是否存在任何差异:

简要要求 - 我必须同时处理200条记录,每条记录只应处理一次。

方法1:

public class Counter {

    private static int count = -1;
    private static final Object lock = new Object();

    public static int getCount() {
        synchronized (lock) {
            return ++count;
        }
    }
}



public class Task implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while((count = Counter.getCount()) < 200) {
            System.out.println(Thread.currentThread().getName() + " " + count);
            // process record # count
        }
    }
}

public class Tester {

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            service.execute(new Task());
        }
        service.shutdown();
        service.awaitTermination(5, TimeUnit.MINUTES);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

方法2:

public class Task2 implements Runnable {

    private int i = 0;

    public Task2(int i) {
        super();
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " " + i);
        // process record # i
    }
}

public class Tester2 {

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        ExecutorService service = Executors.newFixedThreadPool(10); 
        for (int i = 0; i < 200; i++) {
            service.execute(new Task2(i));
        }
        service.shutdown();
        service.awaitTermination(5, TimeUnit.MINUTES);
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
}

两者运行良好并产生相同的输出并且几乎花费相同的时间。 但是,在实际情况中,示例代码中采用的数字将达到数十亿。请建议哪一个在内存和CPU等方面会更好。

感谢。

编辑 - 根据Jimmy T.的建议添加方法3。

方法3:

public class Tester3 {

    public static void main(String[] args) throws InterruptedException {

        int processedRecords = 0;
        int totalRecords = 200;
        int recordsPerThread = 10;
        boolean continueProcess = true;

        ExecutorService service = Executors.newFixedThreadPool(10);

        while(continueProcess) {
            int startIndex = processedRecords;
            int endIndex = startIndex + recordsPerThread - 1;
            if (endIndex >= totalRecords - 1) {
                endIndex = totalRecords - 1;
                continueProcess = false;
            }
            processedRecords = processedRecords + recordsPerThread;
            service.submit(new Task3(startIndex, endIndex));
        }
        service.shutdown();
        service.awaitTermination(5, TimeUnit.MINUTES);
    }
}


public class Task3 implements Runnable {

    private int startIndex = 0;
    private int endIndex = 0;

    public Task3(int startIndex, int endIndex) {
        super();
        this.startIndex = startIndex;
        this.endIndex = endIndex;
    }

    @Override
    public void run() {
        System.out.println("processing records from " + startIndex + " to " + endIndex);
    }
}

3 个答案:

答案 0 :(得分:2)

  

简要要求 - 我必须同时处理200条记录,每条记录只应处理一次。

     

然而,在实际情况中,示例代码中的200个数字将达到数十亿。

与往常一样,答案是&#34;它取决于&#34;。除非你的任务非常快,否则同步或对象创建的开销并不重要。请注意,ExecutorService也必须在内部进行一些同步。

您可以使用AtomicInteger计数器来避免同步并简化代码。但我最有可能选择QueueRunnable poll PriorityQueue,因为它很简单并且给你很大的灵活性:

  • 如果您想优先处理某些记录,可以使用BlockingQueue
  • 如果将所有记录存储在内存中太昂贵(您的队列可以从文件或数据库或其他任何内容中获取),您可以创建自己的队列。

您可以预先填充队列或使用由制作人填充的public class Task3 implements Runnable { @Override public void run() { while (true) { final Integer i = queue.poll(); if (i==null) break; System.out.println(Thread.currentThread().getName() + " " + i); // process record # i } } } 。队列的内容可以是记录索引或(可能更好)记录本身。

每个任务轮询队列并在其排空时终止。

ExecutorService service = Executors.newFixedThreadPool(10); 
for (int i = 0; i < 10; i++) service.execute(new Task3());

您只创建了有限的数量,以保持所有核心的忙碌。

{{1}}

答案 1 :(得分:1)

好吧,我只能看到,在方法1中,JVM还有一个要处理的对象(Conter),而在Counter内部有一个synchronized块,因为它需要花费更多的线程调度程序。必须处理变量计数的访问:

 public static int getCount() {
        synchronized (lock) {
            return ++count;
        }
    }

因此,如果我们谈论处理和性能,似乎方法2会好一点。

答案 2 :(得分:1)

第二种方法更好,因为你没有同步所有线程,但我建议你为每个Task2实例处理多个记录。