一個簡單的java多線程例子

現在有這樣一個任務,有一份手機號列表(20W),有一份話單的列表(10W),要統計哪些手機號沒有出現在話單中,哪些手機號在話單中出現了不止一次。

想到的最直接的方式,就是兩層循環去遍歷,雖然此方法比較笨,但目前還沒有想出更好的辦法。

一開始使用單線程來處理,代碼是隨手寫的並沒有進行重構,只是做一個簡單的說明:

package tool;

import java.util.List;

public class SingleThread
{
	public static void main(String[] args)
	{
		SingleThread st = new SingleThread();

		String userIdPath = "D:\\shell\\store_bak\\tool\\userid.txt";
		List<String> userIds = Util.readUserId(userIdPath);
		List<String> cdrItems = Util.readCdrItem();

		st.process(userIds, cdrItems);
	}

	/**
	 * 
	 * @param userIds
	 * @param cdrItems
	 */
	private void process(List<String> userIds, List<String> cdrItems)
	{
		long startTime = System.currentTimeMillis();
		int count = 0;
		for (String key : userIds)
		{
			String[] uninKeys = key.split("\\s+");
			count = 0;
			for (String cdr : cdrItems)
			{
				if (cdr.contains("|" + uninKeys[0] + "|")
						&& cdr.contains("|" + uninKeys[1] + "|"))
				{
					count++;
				}
			}
		}
		System.out.println((System.currentTimeMillis() - startTime) / 1000);
	}

}

Util中的代碼就不給出了,就是簡單的文件讀取操作,整個過程處理下來速度並不是太快,其中最耗時的操作在contains方法上,一開始使用的並不是contains方法,而是使用的正則表達式匹配,結果發現正則表達式的效率並不高,因此改用contains方法。但是效率還是不太理想。因此考慮使用多線程來處理。

和傳統的生產者消費者不同,這裏實際上只有消費者,因爲產生原始數據幾乎不耗時,最容易想到的辦法就是定義個共享的index標誌,依次互斥的進行+1操作,因此這裏的index就是一個共享的變量,需要進行同步。直接使用jdk中提供的AtomicInteger,代碼如下:

package tool;

import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

public class MutiThread
{
	private static AtomicInteger lock = new AtomicInteger(0);

	public static void main(String[] args)
	{
		MutiThread tool = new MutiThread();
		String userIdPath = "D:\\shell\\store_bak\\tool\\userid.txt";
		List<String> userIds = Util.readUserId(userIdPath);
		List<String> cdrItems = Util.readCdrItem();

		tool.work2(lock, userIds, cdrItems);
	}

	public void work2(AtomicInteger lock, List<String> userIds,
			List<String> cdrItems)
	{
		final long startTime = System.currentTimeMillis();
		CyclicBarrier cb = new CyclicBarrier(5, new Runnable()
		{

			@Override
			public void run()
			{
				System.out.println((System.currentTimeMillis() - startTime) / 1000);
			}
		});
		for (int i = 0; i < 5; i++)
		{
			new Thread(new Worker(userIds, cdrItems, lock, cb)).start();
		}
	}

	class Worker implements Runnable
	{
		private List<String> userIds;
		private List<String> cdrItems;
		private AtomicInteger lock;
		private CyclicBarrier cb;

		public Worker(List<String> userIds, List<String> cdrItems,
				AtomicInteger lock, CyclicBarrier cb)
		{
			this.userIds = userIds;
			this.cdrItems = cdrItems;
			this.lock = lock;
			this.cb = cb;
		}

		@Override
		public void run()
		{
			while (true)
			{
				int index = lock.getAndIncrement();
				if (index >= userIds.size())
					break;
				String id = userIds.get(index);
				process1(id, cdrItems);
			}

			try
			{
				cb.await();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			} catch (BrokenBarrierException e)
			{
				e.printStackTrace();
			}
		}

	}

	private void process1(String id, List<String> cdrItems)
	{
		String[] uninKeys = id.split("\\s+");
		int count = 0;
		for (String cdr : cdrItems)
		{
			if (cdr.contains("|" + uninKeys[0] + "|")
					&& cdr.contains("|" + uninKeys[1] + "|"))
			{
				count++;
			}
		}
	}

}

使用多線程的方式確實能夠提高不少效率,尤其是數據量大的時候,至少是兩倍的速度,這裏的線程數也不是越多越好,因爲JVM對線程的調度也會消耗資源。

針對這個場景,考慮下concurrenthashmap的實現,可以將資源進行分段處理,可以巧妙的避開多線程的資源徵用,因此可以將list分成不同的段,交給不同的線程去處理,代碼如下:

package tool;

import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

public class MutiSegmentMutiThread
{
	private static AtomicInteger lock = new AtomicInteger(0);
	private static int ThreadNum = 10;

	public static void main(String[] args)
	{
		MutiSegmentMutiThread tool = new MutiSegmentMutiThread();
		String userIdPath = "D:\\shell\\store_bak\\tool\\userid.txt";
		List<String> userIds = Util.readUserId(userIdPath);
		List<String> cdrItems = Util.readCdrItem();

		tool.work2(lock, userIds, cdrItems);
	}

	public void work2(AtomicInteger lock, List<String> userIds,
			List<String> cdrItems)
	{
		final long startTime = System.currentTimeMillis();
		CyclicBarrier cb = new CyclicBarrier(ThreadNum, new Runnable()
		{
			@Override
			public void run()
			{
				System.out.println((System.currentTimeMillis() - startTime) / 1000);
			}
		});
		int segmentSize = userIds.size() / ThreadNum;
		int start = 0;
		int end = 0;
		for (int i = 0; i < ThreadNum; i++)
		{
			start = i * segmentSize;
			if (i == ThreadNum - 1)
			{
				end = userIds.size();
			} else
			{
				end = (i + 1) * segmentSize;
			}
			new Thread(new Worker(userIds, cdrItems, cb, start, end)).start();
		}
	}

	class Worker implements Runnable
	{
		private List<String> userIds;
		private List<String> cdrItems;
		private CyclicBarrier cb;
		private int start;
		private int end;

		public Worker(List<String> userIds, List<String> cdrItems,
				CyclicBarrier cb, int start, int end)
		{
			this.userIds = userIds;
			this.cdrItems = cdrItems;
			this.cb = cb;
			this.start = start;
			this.end = end;
		}

		@Override
		public void run()
		{
			for (int i = start; i < end; i++)
			{
				String id = userIds.get(i);
				process1(id, cdrItems);
			}
			try
			{
				cb.await();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			} catch (BrokenBarrierException e)
			{
				e.printStackTrace();
			}
		}

	}

	private void process1(String id, List<String> cdrItems)
	{
		String[] uninKeys = id.split("\\s+");
		int count = 0;
		for (String cdr : cdrItems)
		{
			if (cdr.contains("|" + uninKeys[0] + "|")
					&& cdr.contains("|" + uninKeys[1] + "|"))
			{
				count++;
			}
		}
	}

}

實際測試中第三種方式確實比第二種要快些,但是提升並不是很明顯。以上的代碼只是爲解決問題提供一個思路,想必還能夠繼續優化,如果數據量非常大,可以考慮使用分佈式計算了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章