Issue with Java threads, using Runnable or Thread - java

I'm trying to implement multi-threading using merge sort. I have it making new threads at the point where it cuts an array in half.
The array is sorted depending on the:
[size of the array] vs [how many times I create new threads]
For instance: the array will be sorted if I let it create merely two threads on an array of size 70, but if I let it create 6, it will come back unsorted. One thing I thought it might be is that the threads weren't sync'd, but I used threadName.join()
here is some code: merge.java
import java.util.Random;
public class merge implements Runnable {
int[] list;
int length;
int countdown;
public merge(int size, int[] newList, int numberOfThreadReps, int firstMerge) {
length = size;
countdown = numberOfThreadReps;
list = newList;
if (firstMerge == 1)
threadMerge(0, length - 1);
}
public void run() {
threadMerge(0, length - 1);
}
public void printList(int[] list, int size) {
for (int i = 0; i < size; i++) {
System.out.println(list[i]);
}
}
public void regMerge(int low, int high) {
if (low < high) {
int middle = (low + high) / 2;
regMerge(low, middle);
regMerge(middle + 1, high);
mergeJoin(low, middle, high);
}
}
public void mergeJoin(int low, int middle, int high) {
int[] helper = new int[length];
for (int i = low; i <= high; i++) {
helper[i] = list[i];
}
int i = low;
int j = middle + 1;
int k = low;
while (i <= middle && j <= high) {
if (helper[i] <= helper[j]) {
list[k] = helper[i];
i++;
} else {
list[k] = helper[j];
j++;
}
k++;
}
while (i <= middle) {
list[k] = helper[i];
k++;
i++;
}
helper = null;
}
public void threadMerge(int low, int high) {
if (countdown > 0) {
if (low < high) {
countdown--;
int middle = (low + high) / 2;
int[] first = new int[length / 2];
int[] last = new int[length / 2 + ((length % 2 == 1) ? 1 : 0)];
for (int i = 0; i < length / 2; i++)
first[i] = list[i];
for (int i = 0; i < length / 2 + ((length % 2 == 1) ? 1 : 0); i++)
last[i] = list[i + length / 2];
merge thread1 = new merge(length / 2, first, countdown, 0);// 0
// is
// so
// that
// it
// doesn't
// call
// threadMerge
// twice
merge thread2 = new merge(length / 2
+ ((length % 2 == 1) ? 1 : 0), last, countdown, 0);
Thread merge1 = new Thread(thread1);
Thread merge2 = new Thread(thread2);
merge1.start();
merge2.start();
try {
merge1.join();
merge2.join();
} catch (InterruptedException ex) {
System.out.println("ERROR");
}
for (int i = 0; i < length / 2; i++)
list[i] = thread1.list[i];
for (int i = 0; i < length / 2 + ((length % 2 == 1) ? 1 : 0); i++)
list[i + length / 2] = thread2.list[i];
mergeJoin(low, middle, high);
} else {
System.out.println("elsd)");
}
} else {
regMerge(low, high);
}
}
}
proj4.java
import java.util.Random;
public class proj4 {
public static void main(String[] args) {
int size = 70000;
int threadRepeat = 6;
int[] list = new int[size];
list = fillList(list, size);
list = perm(list, size);
merge mergy = new merge(size, list, threadRepeat, 1);
// mergy.printList(mergy.list,mergy.length);
for (int i = 0; i < mergy.length; i++) {
if (mergy.list[i] != i) {
System.out.println("error)");
}
}
}
public static int[] fillList(int[] list, int size) {
for (int i = 0; i < size; i++)
list[i] = i;
return list;
}
public static int[] perm(int[] list, int size) {
Random generator = new Random();
int rand = generator.nextInt(size);
int temp;
for (int i = 0; i < size; i++) {
rand = generator.nextInt(size);
temp = list[i];
list[i] = list[rand];
list[rand] = temp;
}
return list;
}
}
so TL;DR my array isn't getting sorted by a multithreaded merge sort based on the size of the array and the number of times I split the array by using threads...why is that?

Wow. This was an interesting exercise in masochism. I'm sure you've moved on but I thought for posterity...
The bug in the code is in mergeJoin with the middle argument. This is fine for regMerge but in threadMerge the middle passed in is (low + high) / 2 instead of (length / 2) - 1. Since in threadMerge low is always 0 and high is length - 1 and the first array has (length / 2) size. This means that for lists with an odd number of entries, it will often fail depending on randomization.
There are also a number of style issues which makes this program significantly more complicated and error prone:
The code passes around a size of the arrays when Java has a convenient list.length call which would be more straightforward and safer.
The code duplicates calculations (see length/2) in a number of places.
The code should be able to sort inside the array without creating sub-arrays.
Classes should start with an uppercase letter (Merge instead of merge)
firstMerge should be a boolean
The code names the Thread variable merge1 and the merge variable thread1. Gulp.
The merge constructor calling threadMerge(0,length -1) is strange. I would just put that call after the new call back in proj4. Then firstMerge can be removed.
I would consider switching to having high be one past the maximum value instead of the maximum. We tend to think like for (int i = 0; i < 10; i++) more than i <= 9. Then the code can have j go from low to < middle and k from middle to < high. Better symmetry.
Best of luck.

Related

Why this merge procedure is slightly slower?

Recently I was testing two variations of the merge method in Mergesort and one turns our to be slightly faster than the other. For a large enough input (say, an array of 10-100 million or more randomly ordered elements), one merge method takes around 100ms longer than the other.
Here's the one taking more time:
private static void merge(int[] a, int low, int mid, int hi) {
int temp[] = new int[(hi - low) + 1];
int cLeft = low;
int cRight = mid + 1;
int cTemp = 0;
while (cLeft <= mid && cRight <= hi) {
if (a[cLeft] <= a[cRight]) {
temp[cTemp++] = a[cLeft++];
} else {
temp[cTemp++] = a[cRight++];
}
}
//copy the remaining left elements to the right end
System.arraycopy(a, cLeft, a, low + cTemp, mid - cLeft + 1);
//copy temp to a
System.arraycopy(temp, 0, a, low, cTemp);
}
...and this is the faster one
private static void merge(int[] list, int lowIndex, int midIndex, int highIndex) {
int[] L = new int[midIndex - lowIndex + 2];
for (int i = lowIndex; i <= midIndex; i++) {
L[i - lowIndex] = list[i];
}
L[midIndex - lowIndex + 1] = Integer.MAX_VALUE;
int[] R = new int[highIndex - midIndex + 1];
for (int i = midIndex + 1; i <= highIndex; i++) {
R[i - midIndex - 1] = list[i];
}
R[highIndex - midIndex] = Integer.MAX_VALUE;
int i = 0, j = 0;
for (int k = lowIndex; k <= highIndex; k++) {
if (L[i] <= R[j]) {
list[k] = L[i];
i++;
} else {
list[k] = R[j];
j++;
}
}
}
Both variations of MergeSort are given different arrays of same length with same elements at identical positions as their input. In other words, input of one algorithm is a copy of input of the other.
Although the difference in running time is negligible (the average running time doesn't change, i.e. remains 100ms, no matter how much we increase the size after 1 million mark.), I am eager to know what makes the faster merge faster. For me, the former method is cleaner and easier to implement. However, if the other one remains faster, I probably will switch to that.

4 threaded merge sort algorithm

I want to modify the 2 threaded merge sort to 4 threaded merge sort. First I want to divide the array into 4 equal subarrays(the last subarray might be larger) and assign them into separate threads to sort. Finally, merge 1 and 2 subarrays and 3 and 4 subarrays, then merge already sorted-merged 1 and 2 subarrays with sorted-merged 3 and 4 subarrays.
here is what i have as now:
How can I implement that? Thanks ver much!!!
package four_threaded;
import java.util.Random;
public class four_threaded_merge_sort {
public static void finalMerge(int[] a, int[] b) {
int[] result = new int[a.length + b.length];
int i=0;
int j=0;
int r=0;
while (i < a.length && j < b.length) {
if (a[i] <= b[j]) {
result[r]=a[i];
i++;
r++;
}
else {
result[r]=b[j];
j++;
r++;
}
if (i==a.length) {
while (j<b.length) {
result[r]=b[j];
r++;
j++;
}
}
if (j==b.length) {
while (i<a.length) {
result[r]=a[i];
r++;
i++;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Random rand = new Random();
int[] original = new int[10];
for (int i=0; i<original.length; i++) {
original[i] = rand.nextInt(100);
}
long startTime = System.currentTimeMillis();
int s = original.length / 4 ;
int r = original.length % 4;
//first subarray
int[] subArr1 = new int[s];
System.arraycopy(original, 0, subArr1, 0, s);
//second subarray
int[] subArr2 = new int[s];
System.arraycopy(original, s, subArr2, 0, s);
//third subarray
int[] subArr3 = new int[s];
System.arraycopy(original, 2*s, subArr3, 0, s);
//fourth subarray
int[] subArr4 = new int[s+r];
System.arraycopy(original, 3*s, subArr4, 0, s+r);
Worker runner1 = new Worker(subArr1);
Worker runner2 = new Worker(subArr2);
Worker runner3 = new Worker(subArr3);
Worker runner4 = new Worker(subArr4);
runner1.start();
runner2.start();
runner3.start();
runner4.start();
runner1.join();
runner2.join();
runner3.join();
runner4.join();
finalMerge(runner1.getInternal(), runner2.getInternal());
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("4-thread MergeSort takes: " + (float)elapsedTime/1000 + " seconds");
}
}
class Worker extends Thread {
private int[] internal;
public int[] getInternal() {
return internal;
}
public void mergeSort(int[] array) {
if (array.length > 1) {
int[] left = leftHalf(array);
int[] right = rightHalf(array);
mergeSort(left);
mergeSort(right);
merge(array, left, right);
}
}
public int[] leftHalf(int[] array) {
int size1 = array.length / 2;
int[] left = new int[size1];
for (int i = 0; i < size1; i++) {
left[i] = array[i];
}
return left;
}
public int[] rightHalf(int[] array) {
int size1 = array.length / 2;
int size2 = array.length - size1;
int[] right = new int[size2];
for (int i = 0; i < size2; i++) {
right[i] = array[i + size1];
}
return right;
}
public void merge(int[] result, int[] left, int[] right) {
int i1 = 0;
int i2 = 0;
for (int i = 0; i < result.length; i++) {
if (i2 >= right.length || (i1 < left.length && left[i1] <= right[i2])) {
result[i] = left[i1];
i1++;
} else {
result[i] = right[i2];
i2++;
}
}
}
Worker(int[] arr) {
internal = arr;
}
public void run() {
mergeSort(internal);
}
}
After the 4 threads complete, you could use 2 threads to merge the 4 sub-arrays into 2 sub-arrays, although that probably won't help much since the merging of large arrays will be memory bandwidth limited. You could also use the main thread to do a 4 way merge. On processor with 16 registers, like a PC in 64 bit mode, there are enough registers to deal with 4 sub-arrays via pointers or indices.
If speed is the goal here, allocating a working array one time and using a bottom up merge sort would be faster than top down. To avoid copying, each merge sort alternates merging data between the original and the working array. Each merge sort can determine the number of passes needed, and if it's an odd number, then swap in place for the first pass instead of merging to the other buffer.
If the final pass will be a 4 way merge, then each merge sort thread should end up with the sorted data in the working array. In this case, if it's an even number of passes, then swap in place for the first pass. You may want to do an in place swap for more than 2 elements at a time. Using something like a sorting network, 4 elements can be swapped with 6 if / swap statements. I'm not sure about using other in place sort methods for small sets of elements helps.

Java Fork/Join for large inputs not finishing

I am trying to complete the coding for a parallel version of my sequential code of a 1D median filter on an input of float values. I have been using some smaller lengths of test values to code the parallel version and finally seemed to get the code working, however now on a large dataset of +-36000 elements the code seems to just ramp up CPU resources and not complete whereas my sequential version completes on the same given input. Does anyone know where I am going wrong?
Test input that works: 2, 6, 80, 3, 1 and produces correctly: 2,6,6,3,1
// median filter code
public class MedianFilter extends RecursiveAction {
// filter class variables
float[] numbers;
int filter;
int window;
int length;
int lo;
int hi;
// sequential cutoff set to predetermined value
static final int SEQUENTIAL_CUTOFF = 500;
// array used for recursive calls in parallel code
float[] filtered;
float[] result;
public MedianFilter(float[] numbers, int filter, int lo, int hi) {
this.numbers = numbers;
this.filter = filter;
this.lo = lo;
this.hi = hi;
length = numbers.length;
// the section of floats to be filtered (median section)
window = (filter - 1) / 2;
}
protected void compute() {
filtered = new float[length];
result = new float[length];
if ((hi - lo) < SEQUENTIAL_CUTOFF) {
for (int a = lo; a < hi; a++) {
// iterate window through all elements of the array
for (int i = 0; i < length; i++) {
// fetch boundary elements
if (i < window || i >= length - window) {
result[i] = numbers[i];
}
// fetch elements within filter window
else {
for (int j = 0; j < filter; j++) {
filtered[j] = numbers[i - window + j];
}
// order elements
for (int j = 0; j < filtered.length / 2; j++) {
// get the position of the smallest float
int min = j;
for (int k = j + 1; k < filter; k++) {
if (filtered[k] < filtered[min]) {
min = k;
}
// reorder array for minimum element
float temp = filtered[j];
filtered[j] = filtered[min];
filtered[min] = temp;
}
// result
result[i] = filtered[window];
}
}
}
}
} else {
MedianFilter left = new MedianFilter(filtered, filter, lo, (hi + lo) / 2);
MedianFilter right = new MedianFilter(filtered, filter, (hi + lo) / 2, hi);
left.fork();
right.compute();
left.join();
}
}

Median of Medians algorithm not working consistently

I have implemented the select/median of medians algorithm using the following as a reference http://www.ics.uci.edu/~eppstein/161/960130.html (this has previously been linked here Median of Medians in Java).
My code seems to work for small arrays (~100) and even works for arrays of size 100001 http://pastebin.com/mwRc4Hig (answer 5008), but then fails on an input array of size 10001 http://pastebin.com/YwVBmgDk (answer 4960, my code outputs 4958).
Note that the correct answers for the texts above are equivalent to sorting the array and returning the element at array[array.length / 2], regardless of whether the array size is even or odd.
I'm not sure how to debug this issue. The functionality seems arbitrary and I'm just lost. Here below is my code:
public class MedianOfMedians {
public static void main(String[] args) {
MedianOfMedians mds = new MedianOfMedians();
mds.run();
}
private void run() {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] numArray = new int[n];
for (int i = 0; i < n; i++) {
numArray[i] = in.nextInt();
}
int median = select(numArray, numArray.length / 2);
System.out.print(median);
}
private int select(int[] numArray, int k) {
if (numArray.length <= 10) {
int[] sorted = insertionSort(numArray);
return sorted[k];
}
int divCount = (numArray.length % 5 == 0) ? numArray.length / 5 - 1 : numArray.length / 5;
int[] medOfMed = new int[divCount + 1];
int counter = 0;
int[] subArray;
while (counter <= divCount) {
subArray = splitByFive(counter, divCount, numArray);
medOfMed[counter] = select(subArray, subArray.length / 2);
counter++;
}
int M = select(medOfMed, numArray.length / 10);
List<Integer> lt = new ArrayList<>();
List<Integer> eq = new ArrayList<>();
List<Integer> gt = new ArrayList<>();
for (int i : numArray) {
if (i < M) {
lt.add(i);
} else if (i == M) {
eq.add(i);
} else {
gt.add(i);
}
}
if (k < lt.size()) {
return select(createArray(lt), k);
} else if (k > lt.size() + eq.size()) {
return select(createArray(gt), k - lt.size() - eq.size());
} else {
return M;
}
}
private int[] splitByFive(int splitIter, int divisions, int[] toSplit) {
int numToCopy;
if (splitIter == divisions) {
numToCopy = toSplit.length - (5 * splitIter);
} else {
numToCopy = 5;
}
int[] subArray = new int[numToCopy];
System.arraycopy(toSplit, splitIter * 5, subArray, 0, numToCopy);
return subArray;
}
private int[] createArray(List<Integer> list) {
int[] result = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
result[i] = list.get(i);
}
return result;
}
private int[] insertionSort(int[] numArray) {
for (int i = 1; i < numArray.length; i++) {
int j = i;
while (j - 1 >= 0 && numArray[j] < numArray[j - 1]) {
int temp = numArray[j];
numArray[j] = numArray[j - 1];
numArray[j - 1] = temp;
j--;
}
}
return numArray;
}
}
I don't have time to debug your code, but maybe I can offer a debugging technique for you to try yourself that's useful for recursive algorithms like this.
If there is an input that the algorithm fails on (and there is, as you found) then there is a smallest such input -- and the smaller this input, the easier it is to figure out what's going wrong. Because the algorithm is recursive, you have a nice way to isolate the first place that things go wrong: you can test that the result you are about to return from select() is correct (using a slow, trusted method like copying the data to a temporary buffer, sorting it and then grabbing the half-way element) just before returning the value. Doing this will be much easier if you rearrange the function to use just a single return statement, e.g.:
private int select(int[] numArray, int k) {
int knownCorrectAnswer = selectSlowlyButDefinitelyCorrectly(numArray, k);
int willReturn;
if (numArray.length <= 10) {
int[] sorted = insertionSort(numArray);
willReturn = sorted[k]; // Just remember what we will return
} else { // Need to add else branch here now
...
if (k < lt.size()) {
willReturn = select(createArray(lt), k);
} else if (k > lt.size() + eq.size()) {
willReturn = select(createArray(gt), k - lt.size() - eq.size());
} else {
willReturn = M;
}
} // End of inserted else branch
if (willReturn == knownCorrectAnswer) {
return willReturn;
} else {
yell("First problem occurs with numArray=<...> and k=<...>!");
}
}
yell() should print out the entire problem instance and halt the program (e.g. by throwing an exception). The nice thing about this setup is that you know that when yell() gets called, every call to select() that has already completed was correct -- since if it wasn't, yell() would have already been called and the program would have halted before now. So the output produced by yell() is guaranteed to be the first (not necessarily the smallest, but often that also) subproblem in which things went wrong.

Split array into pieces of X length

Currently I have an array of size N. I'm trying to copy every X amount of bytes from the array.
Example if the array is size 10 and I want arrays of size 3. I'd copy the first 3 elements then the next 3 and the last 1.
Currently I'm using the following algorithm:
int I = 0;
int sub = bytes.length;
int counter = 0;
for (I = 0; I < bytes.length; ++I) {
if (I % 3 == 0 && I != 0) {
NewArray[counter] = Arrays.copyOfRange(bytes, I - 3, I));
sub -= 3;
++counter;
}
}
NewArray[counter] = Arrays.copyOfRange(bytes, I - sub, I)); //Copy remainder.
Is there a more efficient or a more decent way of doing the what I want? This algorithm looks pretty bad =l
Any ideas how I can improve it or at least a hint?
What about this:
int x = 3; // chunk size
int len = bytes.length;
int counter = 0;
for (int i = 0; i < len - x + 1; i += x)
newArray[counter++] = Arrays.copyOfRange(bytes, i, i + x);
if (len % x != 0)
newArray[counter] = Arrays.copyOfRange(bytes, len - len % x, len);
Here's a convenient method that converts a byte[] to an array of byte[]'s. So, the result is a byte[][].
public byte[][] splitBytes(final byte[] data, final int chunkSize)
{
final int length = data.length;
final byte[][] dest = new byte[(length + chunkSize - 1)/chunkSize][];
int destIndex = 0;
int stopIndex = 0;
for (int startIndex = 0; startIndex + chunkSize <= length; startIndex += chunkSize)
{
stopIndex += chunkSize;
dest[destIndex++] = Arrays.copyOfRange(data, startIndex, stopIndex);
}
if (stopIndex < length)
dest[destIndex] = Arrays.copyOfRange(data, stopIndex, length);
return dest;
}
Some advantages compared to the previous best answer:
The for condition uses a <= which makes more sense than < ... + 1.
Putting the stop-index in a temporary field reduces the number of calculations in the last if block.
(Unit tested)
Few things to do here:
First, common conventions frown apon using capitals to start variable names, change the I and NewArray variables to 'i' and 'newArray' respectively.
Then, your code does not work because your first time through the loop, i-3 will lead to an IndexOutOfBounds exception.....
Finally, you do not show how you set the size of the newArray array.
int sublen = 3; // how many elements in each sub array.
int size = ((bytes.length - 1) / sublen) + 1; // how many newArray members we will need
byte[][] newArray = new byte[size][];
int to = byte.length;
int cursor = size - 1;
int from = cursor * sublen;
while (cursor >= 0) {
newArray[cursor] = Arrays.copyOfRange(bytes, from, to);
to = from;
from -= sublen;
cursor --;
}
Here's my implementation for this, it will split your array in sub-arrays of up to a maximum size you decide on, and put the sub-arrays into a list of arrays. The last array will be smaller if the size of the array is not a multiple of the maximum size chosen.
import java.util.Arrays;
...
public static <T> List<T[]> splitArray(T[] items, int maxSubArraySize) {
List<T[]> result = new ArrayList<T[]>();
if (items ==null || items.length == 0) {
return result;
}
int from = 0;
int to = 0;
int slicedItems = 0;
while (slicedItems < items.length) {
to = from + Math.min(maxSubArraySize, items.length - to);
T[] slice = Arrays.copyOfRange(items, from, to);
result.add(slice);
slicedItems += slice.length;
from = to;
}
return result;
}
Here is a function to split arrays, you can use below main method to test it.
private static List<Integer[]> splitArray(Integer[] originalArray, int chunkSize) {
List<Integer[]> listOfArrays = new ArrayList<Integer[]>();
int totalSize = originalArray.length;
if(totalSize < chunkSize ){
chunkSize = totalSize;
}
int from = 0;
int to = chunkSize;
while(from < totalSize){
Integer[] partArray = Arrays.copyOfRange(originalArray, from, to);
listOfArrays.add(partArray);
from+= chunkSize;
to = from + chunkSize;
if(to>totalSize){
to = totalSize;
}
}
return listOfArrays;
}
Testing method:
public static void main(String[] args) {
List<Integer> testingOriginalList = new ArrayList<Integer>();
for(int i=0;i<200;i++){
testingOriginalList.add(i);
}
int batchSize = 51;
Integer[] originalArray = testingOriginalList.toArray(new Integer[]{});
List<Integer[]> listOfArrays = splitArray(originalArray, batchSize);
for(Integer[] array : listOfArrays){
System.out.print(array.length + ", ");
System.out.println(Arrays.toString(array));
}
}
I know that this question is pretty old but hey, someone could search for another clean Java answer for this common question.
It you are working with List (Java 7), there is a pretty simple and clean method to get a portion of a list : List.subList( fromIndex, toIndex )
It's straightforward to use. If I take the question example, it would be like :
int chunkSize = 3;
int counter = 0;
// bytes must be a List like an ArrayList
List<Byte> byteList = Arrays.asList(bytes);
int length = byteList.size();
for (int fromIndex = 0; fromIndex < length; fromIndex += chunkSize) {
int toIndex = fromIndex + chunkSize;
if(toIndex > length){
toIndex = length;
}
NewArray[counter] = byteList.subList(fromIndex, toIndex);
counter++;
}
// Now NewArray[] contain sub array and the last one is of the remaining length
To get ride of the 'counter', some could change the way NewArray is build for a List approach as well, with something like :
// NewArray must be a List<List<Byte>>
NewArray.addAll(byteList.subList(fromIndex, toIndex));
Hope this will help someone in the future !
You can use split with a special regular expression:
System.out.println(Arrays.toString(
"Thisismystringiwanttosplitintogroupswith4chareach".split("(?<=\\G.{4})")
));
Credit to earlier post by Alan Moore. Please visit and vote up.
If actually you need quite big chunks, and don't want to modify their contents independently, consider reusing the same initial array by means of ByteBuffer.wrap() and then slice() repeatedly. This would prevent unnecessary copying and memory waste.
import java.util.Arrays;
public class Test {
private void run() {
try {
byte[] cfsObjIds = "abcdefghij".getBytes();
System.out.println(Arrays.toString(cfsObjIds));
final int chunkSize = 4;
System.out.println("Split by " + chunkSize + ":");
int objQty = cfsObjIds.length;
for (int i = 0; i < objQty; i += chunkSize) {
int chunkUpperLimit = Math.min(objQty, i + chunkSize);
byte[] cfsIdsChunk = Arrays.copyOfRange(cfsObjIds, i, chunkUpperLimit);
System.out.println(Arrays.toString(cfsIdsChunk));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
new Test().run();
}
}

Categories