我试图在Java中创建一个并行快速排序,我认为这是一个天真的快速排序(因为我还没有研究过接口执行程序等)
一旦完成所有线程,我需要一种打印排序数组的方法。但是我不知道我将提前有多少线程...所以我这样做是为了等待每次递归使用join()方法..所以调用的第一个连接方法必须等到所有其他线程都完成..对吗?
这样当我在main()(打印数组)中执行我的最后两行时,我可以确定我的所有线程都已完成...
所以我有两个问题..
这是一个并行运行的多线程程序,对吧?或者我是否犯了一些错误,它实际上是以线性方式运行线程之后?
我的解决方案是否在主方法中显示已排序的数组?
这是我的代码:
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);
}
}
}
答案 0 :(得分:3)
如果我们知道线程的总数,我们可以使用CountDownLatch初始化线程数。但由于我们不知道线程的数量,我们需要一个扩展的CountDownLatch,它允许在创建后增加计数器。不幸的是,我们不能只扩展CountDownLatch类,因为底层计数器是私有的。一种方法是复制CountDownLatch的原始代码以访问底层计数器。更简洁的方法是扩展Semaphore以访问reducePermits
方法,就像在Reduceable Semaphore中一样。原则上,CountDownLatch和Semaphore是类似的工具,但在内部计数器的解释上有所不同:第一个指控否决权,后者计算许可数。
整个想法是在创建或启动线程时减少许可数量,并在方法run()
结束时减少释放许可。许可证的初始数量为1,因此如果没有线程启动,则主程序自由完成。请注意,减少方法run()
开头的许可数量为时已晚。
要获得非常好的工作代码,您还需要使用具有固定线程数的线程池,并对小型数组进行串行排序。
答案 1 :(得分:1)
是的,您的代码并行运行。结果打印看起来也很好。
一个问题是你创建了一个巨大的线程数:在最低级别,你将拥有与列表元素一样多的线程。并且你没有捕获由此引起的异常,所以你不会知道(在你的主线程中)这不能按预期工作。
您应该限制新线程的级别数。一旦你通过了3个级别,你将拥有大约2个 3 = 8个线程,这应该足以让所有核心在大多数合理的机器上忙碌。然后,您可以让其余的计算继续进行其他线程的分支。您可以通过将其他参数branching
传递给quicksort
方法来实现。在3
构造函数的调用中将其设置为QuickSortWithThreads
,并在每次调用时递减它。一旦计数达到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
接口来执行此操作。您可以创建ThreadPoolExecutor
来管理线程,并将每个递归调用作为Runnable
的实例传递(可能与您的QuickSortThread
类似)。这种方法的一个主要问题是检测终止。特别是如果您想在发生错误时避免死锁。因此,最好使用ForkJoinTask
,因为在这种情况下,您可以让每个任务等待其他子项的结束,与您编写的内容非常相似,并且您仍然可以限制实际线程的数量在关联的ForkJoinPool
中。如果您没有返回值,那么您的实际实现最好使用RecursiveAction
,ForkJoinTask
的特化,文档中包含的示例与您的场景非常相似。
答案 2 :(得分:1)
线程的行为方式取决于您的硬件。使用单核CPU并且没有超线程,计算机一次一行地逐个线程地线程处理1个线程。如果您有超线程和/或多核,它们可以同时运行多行。对examplethread.join()的调用使调用线程等到examplethread完成其作业(通过从run()方法返回)。 如果你创建一个线程,然后2行调用join,你几乎就会有多线程同步任务非常类似于使它成为单线程。
我建议创建一个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);
}
}
// [...]