標籤:
java多線程-兩種實際應用情境類比 轉自 薛定諤的貓
(一)
先說說第一個,類比對資訊的發送和接收。情境是這樣的:
就像筆者之前做的訊息的發送,一個是伺服器,一個是用戶端。發送的話,要保證資訊100%的發送給用戶端,那麼發給用戶端之後,用戶端返回一個訊息告訴伺服器,已經收到。當伺服器一直沒有收到用戶端返回的訊息,那麼伺服器會一直發送這個資訊,直到用戶端發送回確認資訊,這時候再重複資料刪除發送的這個資訊。
為了類比這個情境,這裡寫兩個線程,一個是發送,一個是接收,把發送的資訊,要儲存到安全執行緒的對象裡面,防止發生安全執行緒問題,這裡採用concurrenthashmap。
發送代碼:
package com.TestThread;/* * * @author 薛定餓的貓 * * */import java.util.Map.Entry;import java.util.concurrent.ConcurrentHashMap;public class PushThread extends Thread { @Override public void run() { // TODO Auto-generated method stub try { sleep(6000); while(MainThread.pushmessage.size()>0){ //重發訊息 for(Entry<Integer, String> hashMap:MainThread.pushmessage.entrySet()){ System.out.println("訊息id:"+hashMap.getKey()+"未發送成功,在此重發:"+hashMap.getValue()); } sleep(1000); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
發送代碼,是不斷遍曆記憶體對象councurrenthashmap,從中取出資訊,不斷的重發。其中MainThread.pushmessage是記憶體對象,在最後一段代碼中有定義。
當確認接收到資訊後,另外一個線程來刪除記憶體對象。
刪除的代碼:
package com.TestThread;/* * * @author 薛定餓的貓 * * */import java.util.Map.Entry;public class RemoveThread extends Thread { @Override public void run() { // TODO Auto-generated method stub try { for (int i = 0; i < 10000; i++) { sleep(2000); for(Entry<Integer, String> map:MainThread.pushmessage.entrySet()){ if (map.getKey()==i) { System.out.println("成功收到id為:"+map.getKey()+"返回的資訊,刪除該元素"); MainThread.pushmessage.remove(map.getKey()); } } System.out.println("記憶體對象中的元素數量為:"+MainThread.pushmessage.size()); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
這裡是來刪除已收到的資訊,然後從記憶體中刪除,不再發送。
然後寫一個主類入口:
package com.TestThread;/* * * @author 薛定餓的貓 * * */import java.util.concurrent.ConcurrentHashMap;public class MainThread { public static ConcurrentHashMap<Integer, String> pushmessage=new ConcurrentHashMap<Integer,String>(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { pushmessage.put(i, "該訊息是id為"+i+"的訊息"); } Thread pushThread=new PushThread(); Thread remove=new RemoveThread(); pushThread.start(); remove.start(); for (int i = 10; i < 20; i++) { pushmessage.put(i, "又一波到來,訊息是id為"+i+"的訊息"); } }}
這樣兩個線程可以輪流的進行各自的事情,並且不會造成資料安全的問題。用這種方式,再結合Androidpn的推送機制,會更加符合實際生產中的應用。
(二)多線程同步計數器
多線程同步計數器,按道理也是可以按照上面的方式來進行處理,定義一個像concurrenthashmap的變數。在java中,確實也有另外一種變數,原子變數Atomic,有AtomicLong,AtomicInteger,AtomicReference這些。
如果在多線程環境下要給一些值賦唯一id的話,這個時候,就要考慮這個id的安全性問題,也就是一致性的問題,不能重複。這裡有兩個實現的代碼:
package com.test;public class ThreadCount { public static void main(String[] args) { Thread[] threads=new Thread[10000]; for (int i = 0; i < 10000; i++) { threads[i]=new AThread(); threads[i].start(); } }}class AThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub @SuppressWarnings("unused") Counter counter=new Counter(); System.out.println(Counter.calNum()); } }class Counter{ private static long num; public Counter(){ synchronized (Counter.class) { num++; } } public static synchronized long calNum(){ return num; }}
這裡建立了10000個線程,每個線程都來訪問這個計數器,在構造方法中來進行值的遞增。
在計數器中,有兩次用到同步,很多人都說用同步,經常會對效能造成影響。於是,用第二種的原子變數,這個效能應該會更好。
代碼:
package com.test;import java.util.concurrent.atomic.AtomicLong;public class ThreadCount { public static void main(String[] args) { Thread[] threads=new Thread[10000]; for (int i = 0; i < 10000; i++) { threads[i]=new AThread(); threads[i].start(); } }}class AThread extends Thread{ @Override public void run() { System.out.println(MyCounter.calNum()); } }class Counter{ private static long num; public Counter(){ synchronized (Counter.class) { num++; } } public static synchronized long calNum(){ return num; }}class MyCounter{ private static AtomicLong num=new AtomicLong(); public static synchronized long calNum(){ return num.incrementAndGet(); }}
這樣寫的話,在調用這個計數器的時候,直接不需要再new一個MyCounter對象。
這樣可以作為工具類,直接調用MyCounter的calNum方法。
java多線程 —— 兩種實際應用情境類比