最近做的偏向並發了,因為以后消息會眾多,所以,jms等多個線程操作數據的時候,對共享變量,這些要很注意,以防止發生線程不安全的情況。
(一)
先說說第一個,模擬對信息的發送和接收。場景是這樣的:
就像筆者之前做的消息的發送,一個是服務器,一個是客戶端。發送的話,要保證信息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方法。