简体   繁体   English

多线程

[英]Multi - threading

I have tried to create a parallel quicksort in Java which I assume is a naive one (cause I haven't studied yet Interface Executor etc) 我试图在Java中创建一个并行的快速排序,我认为这是一个幼稚的操作(因为我还没有研究Interface Executor等)

I needed a way to print the sorted array once all the threads are done..but I didn't know how many threads I am going to have in advance.. so I was doing it in a way that it will wait each time recursively with the join() method.. so the first join method that was invoked has to wait till all the other threads are done.. right ? 我需要一种方法来在所有线程完成后打印排序后的数组..但是我不知道我要预先拥有多少个线程..所以我这样做的方式是每次递归等待使用join()方法..因此,被调用的第一个join方法必须等待,直到所有其他线程都完成了。

In that way when I execute my last two lines in main() ( of the printing array) I can be sure that all my threads are done... 这样,当我在打印数组的main()中执行最后两行时,我可以确定所有线程都已完成...

so I have two questions .. 所以我有两个问题..

  1. It is a multi-threading program that runs in parallel, right ? 这是一个并行运行的多线程程序,对吗? or am I making some mistakes that it actually runs in a linear way thread after thread ? 还是我犯了一些错误,说它实际上是一个接一个的线性运行?

  2. was I correct with my solution for displaying the sorted array in the main method? 我在主方法中显示排序数组的解决方案是否正确?

Here is my code: 这是我的代码:

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> array = new ArrayList();
        //please assume that I have invoked the input for the array from the user 
        QuickSortWithThreads obj = new QuickSortWithThreads(array,0 ,array.size()-1 );
        for(int i = 0; i < array.size(); i++)
            System.out.println(array.get(i));
    }
}

public class QuickSortWithThreads {
    public QuickSortWithThreads(ArrayList <Integer> arr, int left, int right){  
        quicksort(arr, left, right);
    }

    static void  quicksort(ArrayList <Integer> arr, int left, int right)  {
        int pivot;

        if(left<right){
            pivot = partition(arr, left, right);    
            QuickSortThread threadLeftSide = new QuickSortThread(arr, pivot + 1, right);
            threadLeftSide.start();  
            quicksort(arr, left, pivot - 1);
            try {
                threadLeftSide.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }           
        }
    }

    static  int partition(ArrayList<Integer> arr, int left, int right)  {
        int pivot = arr.get(right);
        int i = left -1;
        for( int j = left; j <= right - 1; j++) {
            if (arr.get(j) <= pivot){
                i = i + 1;
                exchange(arr, i, j);
            }
        }
        exchange(arr, i + 1, right);
        return i + 1;
    }
    static void exchange(ArrayList<Integer> arr, int i, int j) {
        int swap = arr.get(i);
        arr.set(i, arr.get(j)); 
        arr.set(j, swap);
    }

    private static class QuickSortThread extends Thread {
        int right;
        int left;
        ArrayList<Integer> refArray; 

        public QuickSortThread(ArrayList<Integer> array, int left, int right) {
            this.right = right;
            this.left = left;
            refArray = new ArrayList<Integer>();
            refArray = array;   
        }

        public void run() {
            quicksort(refArray, left, right);
        }
    }
}

If we knew the overall number of threads, we could use CountDownLatch initialized with the number of threads. 如果我们知道线程的总数,则可以使用以线程数初始化的CountDownLatch But as we don't know the number of threads, we need an extended CountDownLatch which allows to increase the counter after its creation. 但是由于我们不知道线程数,我们需要一个扩展的CountDownLatch,它允许在创建计数器后增加它。 Unfortunately we cannot just extend the class CountDownLatch as underlying counter is private. 不幸的是,由于基础计数器是私有的,我们不能仅扩展CountDownLatch类。 One way is to duplicate the original code of CountDownLatch to make access to the underlying counter. 一种方法是复制CountDownLatch的原始代码以访问基础计数器。 Less verbose way is to extend Semaphore to get access to the reducePermits method as it is done in Reduceable Semaphore . 较不冗长的方法是扩展信号量,以获取对reducePermits方法的访问,就像在Reduceable Semaphore中所做的那样 In principle, CountDownLatch and Semaphore are similar tools but differ in interpretation of the internal counter: the first counts vetoes and the latter counts permits. 原则上,CountDownLatch和Semaphore是相似的工具,但内部计数器的解释有所不同:前者计数否决权,后者计数许可。

The whole idea is to reduce the number of permits when a thread is created or started, and release permit when it is finished, at the end of the method run() . 整个想法是在方法run()的末尾减少创建或启动线程时的许可数量,并在线程完成时释放许可。 Initial number of permits is 1, so that if no threads started, the main procedure finishes freely. 许可的初始数量为1,因此,如果没有启动线程,则主过程可以自由完成。 Note that reducing the number of permits at the beginning of the method run() is too late. 请注意,在方法run()的开头减少许可的数量为时已晚。

To get really good working code, you need also use a thread pool with fixed number of threads, and make sorting serially for small arrays. 为了获得真正好的工作代码,您还需要使用具有固定线程数的线程池,并对小型数组进行串行排序。

General opinion 一般意见

Yes, your code runs in parallel. 是的,您的代码可以并行运行。 And the result printing looks all right as well. 结果打印看起来也不错。

Limiting number of threads via depth 通过深度限制螺纹数量

One problem is the fact that you create a huge number of threads: at the lowest level, you'll have approximately as many threads as there are list elements. 一个问题是您创建了大量的线程:在最低级别,您将拥有与列表元素几乎一样多的线程。 And you don't catch exceptions resulting from this, so you'll not know (in your main thread) that this didn't work as intended. 而且您不会捕获由此产生的异常,因此您不会(在您的主线程中)知道这没有按预期进行。

You should probably limit the number of levels for which you spwan new threads. 您可能应该限制增加新线程的级别数。 Once you have passes the for say 3 levels, you'll have about 2 3 =8 threads, which should be enough to keep all cores busy on most reasonable machines. 一旦您通过了说3个级别,您将拥有大约2 3 = 8个线程,这应该足以使大多数核心在最合理的计算机上忙碌。 You can then let the rest of the computation proceed wthout branching off further threads. 然后,您可以继续其余的计算而无需分支其他线程。 You could do that by passing an additional parameter branching to your quicksort method. 您可以通过将附加的参数branching传递给quicksort方法来实现。 Set that to 3 in the invocation from the QuickSortWithThreads constructor, and decrement it on every call. QuickSortWithThreads构造函数的调用中将其设置为3 ,并在每次调用时将其递减。 Don't branch once the count reaches 0 . 一旦计数达到0不要分支。 This will give you the following calls: 这将给您以下电话:

quicksort(3, …)
  quicksort(2, …)
    quicksort(1, …)
      quicksort(0, …)
      quicksort(0, …)
    quicksort(1, …)
      quicksort(0, …)
      quicksort(0, …)
  quicksort(2, …)
    quicksort(1, …)
      quicksort(0, …)
      quicksort(0, …)
    quicksort(1, …)
      quicksort(0, …)
      quicksort(0, …)

Since each non-leaf call shares a thread with one of its children, you can deduct the maximum of 8 threads from the number of leafs above. 由于每个非叶子调用与其子对象共享一个线程,因此您可以从上述叶子数量中减去最多8个线程。

Limiting number of threads via Executors 通过执行器限制线程数

As an alternative to this home-made way of restricting the number of threads, you might of course do this using the Executor interface you mentioned. 作为这种限制线程数的自制方法的替代方法,您当然可以使用您提到的Executor接口来Executor此操作。 You could create a ThreadPoolExecutor to manage your threads, and pass each recursive invocation as an instance of Runnable (which might look similar to your QuickSortThread ). 您可以创建一个ThreadPoolExecutor来管理您的线程,并将每个递归调用作为Runnable的实例传递(该外观可能类似于QuickSortThread )。 One major problem with this approach is detecting termination. 这种方法的一个主要问题是检测终止。 Particularly if you want to avoid deadlock in case of an error. 尤其是要避免出现错误时发生死锁。 So it might be better to use a ForkJoinTask instead, since in that case you can have each task wait on the conclusion of its other child, very similar to what you wrote, and you can still limit the number of actual threads in the associated ForkJoinPool . 因此,最好改用ForkJoinTask ,因为在这种情况下,您可以让每个任务等待其他子项的结束,这与您编写的内容非常相似,并且您仍然可以限制关联的ForkJoinPool的实际线程数。 Your actual implementation would best use RecursiveAction , a specialization of ForkJoinTask if you have no return value, for which the documentation contains an example very similar to your scenario. 如果您没有返回值,那么实际的实现将最好使用RecursiveAction ,这是ForkJoinTask ,其文档中包含与您的场景非常相似的示例。

The way your threads behave depend on your hardware. 线程的行为方式取决于硬件。 With a single core CPU and no hyperthreading, computer processes 1 thread at a time line by line thread by thread in a loop. 使用单核CPU且没有超线程,计算机一次循环地逐个线程处理一个线程。 If you have hyperthreading and/or multiple cores, they can run multiple lines simultaneously. 如果您具有超线程和/或多个内核,它们可以同时运行多行。 A call to examplethread.join() makes the calling thread to wait until examplethread finishes its job (by returning from run() method). 对examplethread.join()的调用使调用线程等待,直到examplethread完成其工作(通过从run()方法返回)。 if you make a thread and 2 lines later call for join you will pretty much have multithreaded synchronized task very similar to making it singlethreaded. 如果您创建一个线程,然后在两行之后调用join,则您将拥有多线程同步任务,这与使它成为单线程非常相似。

Id suggest to make an ArrayList and add each thread to the list, after all threads are set and working you call a id建议创建一个ArrayList并将每个线程添加到列表中,所有线程都设置好并工作后,您可以调用

for(Thread t : mythreadlist) {
    try {
        t.join();
    } catch (InterruptedException e) { System.err.println("Interrupted Thread"); } 
} 

to make your application wait for all threads to exit. 使您的应用程序等待所有线程退出。

edit: 编辑:

// [...]
public class QuickSortWithThreads {
    ArrayList<QuickSortThread> threads = new ArrayList<>();
public QuickSortWithThreads(ArrayList <Integer> arr, int left, int right){  
    quicksort(arr, left, right); // Pretty much make your threads start their jobs
    for(Thread t : threads) { // Then wait them to leave.
        try {
            t.join();
        } catch (InterruptedException e) { System.err.println("Interrupted Thread"); } 
    } 
}
// [...]
static void  quicksort(ArrayList <Integer> arr, int left, int right)  {
    int pivot;

    if(left<right){
        pivot = partition(arr, left, right);    
        QuickSortThread threadLeftSide = new QuickSortThread(arr, pivot + 1, right);
        threadLeftSide.start();
        threads.add(threadLeftSide());
        // 
        quicksort(arr, left, pivot - 1);  
    }
}
// [...]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM