繁体   English   中英

多线程

[英]Multi - threading

我试图在Java中创建一个并行的快速排序,我认为这是一个幼稚的操作(因为我还没有研究Interface Executor等)

我需要一种方法来在所有线程完成后打印排序后的数组..但是我不知道我要预先拥有多少个线程..所以我这样做的方式是每次递归等待使用join()方法..因此,被调用的第一个join方法必须等待,直到所有其他线程都完成了。

这样,当我在打印数组的main()中执行最后两行时,我可以确定所有线程都已完成...

所以我有两个问题..

  1. 这是一个并行运行的多线程程序,对吗? 还是我犯了一些错误,说它实际上是一个接一个的线性运行?

  2. 我在主方法中显示排序数组的解决方案是否正确?

这是我的代码:

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);
        }
    }
}

如果我们知道线程的总数,则可以使用以线程数初始化的CountDownLatch 但是由于我们不知道线程数,我们需要一个扩展的CountDownLatch,它允许在创建计数器后增加它。 不幸的是,由于基础计数器是私有的,我们不能仅扩展CountDownLatch类。 一种方法是复制CountDownLatch的原始代码以访问基础计数器。 较不冗长的方法是扩展信号量,以获取对reducePermits方法的访问,就像在Reduceable Semaphore中所做的那样 原则上,CountDownLatch和Semaphore是相似的工具,但内部计数器的解释有所不同:前者计数否决权,后者计数许可。

整个想法是在方法run()的末尾减少创建或启动线程时的许可数量,并在线程完成时释放许可。 许可的初始数量为1,因此,如果没有启动线程,则主过程可以自由完成。 请注意,在方法run()的开头减少许可的数量为时已晚。

为了获得真正好的工作代码,您还需要使用具有固定线程数的线程池,并对小型数组进行串行排序。

一般意见

是的,您的代码可以并行运行。 结果打印看起来也不错。

通过深度限制螺纹数量

一个问题是您创建了大量的线程:在最低级别,您将拥有与列表元素几乎一样多的线程。 而且您不会捕获由此产生的异常,因此您不会(在您的主线程中)知道这没有按预期进行。

您可能应该限制增加新线程的级别数。 一旦您通过了说3个级别,您将拥有大约2 3 = 8个线程,这应该足以使大多数核心在最合理的计算机上忙碌。 然后,您可以继续其余的计算而无需分支其他线程。 您可以通过将附加的参数branching传递给quicksort方法来实现。 QuickSortWithThreads构造函数的调用中将其设置为3 ,并在每次调用时将其递减。 一旦计数达到0不要分支。 这将给您以下电话:

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, …)

由于每个非叶子调用与其子对象共享一个线程,因此您可以从上述叶子数量中减去最多8个线程。

通过执行器限制线程数

作为这种限制线程数的自制方法的替代方法,您当然可以使用您提到的Executor接口来Executor此操作。 您可以创建一个ThreadPoolExecutor来管理您的线程,并将每个递归调用作为Runnable的实例传递(该外观可能类似于QuickSortThread )。 这种方法的一个主要问题是检测终止。 尤其是要避免出现错误时发生死锁。 因此,最好改用ForkJoinTask ,因为在这种情况下,您可以让每个任务等待其他子项的结束,这与您编写的内容非常相似,并且您仍然可以限制关联的ForkJoinPool的实际线程数。 如果您没有返回值,那么实际的实现将最好使用RecursiveAction ,这是ForkJoinTask ,其文档中包含与您的场景非常相似的示例。

线程的行为方式取决于硬件。 使用单核CPU且没有超线程,计算机一次循环地逐个线程处理一个线程。 如果您具有超线程和/或多个内核,它们可以同时运行多行。 对examplethread.join()的调用使调用线程等待,直到examplethread完成其工作(通过从run()方法返回)。 如果您创建一个线程,然后在两行之后调用join,则您将拥有多线程同步任务,这与使它成为单线程非常相似。

id建议创建一个ArrayList并将每个线程添加到列表中,所有线程都设置好并工作后,您可以调用

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

使您的应用程序等待所有线程退出。

编辑:

// [...]
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