多线程

时间:2013-05-08 07:29:03

标签: java multithreading

我试图在Java中创建一个并行快速排序,我认为这是一个天真的快速排序(因为我还没有研究过接口执行程序等)

一旦完成所有线程,我需要一种打印排序数组的方法。但是我不知道我将提前有多少线程...所以我这样做是为了等待每次递归使用join()方法..所以调用的第一个连接方法必须等到所有其他线程都完成..对吗?

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

所以我有两个问题..

  1. 这是一个并行运行的多线程程序,对吧?或者我是否犯了一些错误,它实际上是以线性方式运行线程之后?

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

  3. 这是我的代码:

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

3 个答案:

答案 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中。如果您没有返回值,那么您的实际实现最好使用RecursiveActionForkJoinTask的特化,文档中包含的示例与您的场景非常相似。

答案 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);  
    }
}
// [...]