一個簡單的java多線程例子,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方法,而是使用的Regex匹配,結果發現Regex的效率並不高,因此改用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(){@Overridepublic 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;}@Overridepublic 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(){@Overridepublic 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;}@Overridepublic 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++;}}}}
實際測試中第三種方式確實比第二種要快些,但是提升並不是很明顯。以上的代碼只是為解決問題提供一個思路,想必還能夠繼續最佳化,如果資料量非常大,可以考慮使用分散式運算了。