public static void matchListings(List<Listing> listings, final Map<String, Product> productMap, final ManufacturerLookup manufacturerLookup) {
		
		System.out.println("Matching listings...");
		
//		// single threaded implementation
//		for(Listing listing : listings) {
//			
//			// match product name
//			String productName = manufacturerLookup.lookupProductName(listing);
//			if(productName == null) {
//				continue;
//			}
//			
//			// add to the product's listings
//			Product product = productMap.get(productName);
//			if(product == null) {
//				continue;
//			}
//			
//			product.listings.add(listing);
//		}
		
		// mutli-threaded mostly because it was easy to implement for this algorithm 
		// and does speed up the matching a lot for multi-core machines.
		
		// make a thread safe copy to use as the source (linked list since we are removing from the front)
		final List<Listing> source = Collections.synchronizedList(new LinkedList<Listing>(listings));
		
		// objects used to signal that all threads are done
		final Object signal = new Object();
		final AtomicInteger remainingThreads = new AtomicInteger();
		
		for(int i = 0; i < Challenge.THREADS; ++i) {
			
			// another thread added
			remainingThreads.incrementAndGet();
			
			// very simple threads implementation of the above code
			Runnable runner = new Runnable() {
				public void run() {
					while(true) {
						
						// remove head items until collection is empty
						Listing listing;
						try {
							listing = source.remove(0);
						} catch (IndexOutOfBoundsException e) {
							// lists throw IndexOutOfBoundsException when remove is called on an empty list
							// we will use that as a signal for done
							break;
						}
					
						// match product name
						String productName = manufacturerLookup.lookupProductName(listing);
						if(productName == null) {
							continue;
						}
											
						Product product = productMap.get(productName);
						if(product == null) {
							continue;
						}
						
						// add to the product's listings
						// product.listings collection is thread safe
						product.listings.add(listing);
					}
					
					// signal when all threads are done
					int remaining = remainingThreads.decrementAndGet();
					if(remaining <= 0) {
						synchronized (signal) {
							signal.notify();
						}
					}
				}
			};
			
			// run the matcher
			Thread thread = new Thread(runner);
			thread.start();
		}
		
		// wait for all threads to finish
		while(remainingThreads.get() > 0) {
			try {
				synchronized (signal) {
					signal.wait();
				}
			} catch (InterruptedException e) {
				// dont care, try again
			}
		}
	}