Map在多線程中使用

背景:

在實際操作中經常會遇到一種場景,我們需要用一個成員屬性Map來保存信息,這時我們可能起兩個線程甚至更多,一個線程用來給Map裝值,另外一個線程用來每隔一段時間就去Map那取值並清空Map。

實現:

根據上面場景需求,很簡單,刷刷刷...,代碼寫好了,main thread用來給Map裝東西,TimeTask  thread用來取Map裏面的東西

public class AsyMap {
	
	private static Map<Integer, String> map = new HashMap<Integer, String>();
	
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println(Thread.currentThread().getName());
		//啓動時間操作類,每隔一段時間取值
		new Timer().schedule(new MyTask(), 500, 500);
		int i = 1;
		while(true){
			map.put(i, "content msg" + i);
			i++;
			Thread.sleep(200);
		}
	}
	
	
	static class MyTask extends TimerTask{

		@Override
		public void run() {
			if (map.size() > 0) {
					Set<Entry<Integer, String>> entrySet = map.entrySet();
					for (Entry<Integer, String> entry : entrySet) {
						System.out.println(entry.getValue());
					}
					map.clear();
				}
				
			}
		}

}
但是運行之後問題就來了。


原因是多線程的異步,但task在遍歷Map取值是,Main thread 也還在往Map中裝,這是Map不能容忍的,於是就罷工了。哦原來是異步引起的原因,好竟然HashMap異步不安全,那我用Hashtable,給它加鎖總可以了吧!好一通改後代碼成這樣了

public class AsyTable {
	
	private static Map<Integer, String> table = new Hashtable<Integer, String>();
	
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println(Thread.currentThread().getName());
		new Timer().schedule(new MyTask(), 500, 500);
		int i = 1;
		while(true){
			table.put(i, "content msg" + i);
			i++;
			Thread.sleep(200);
		}
	}
	
	
	static class MyTask extends TimerTask{

		@Override
		public void run() {
			if (table.size() > 0) {
					Set<Entry<Integer, String>> entrySet = table.entrySet();
					for (Entry<Integer, String> entry : entrySet) {
						System.out.println(entry.getValue());
					}
					table.clear();
				}
				
			}
		}

}

再重新運行,但不幸的是同樣的問題再次爆出,這下不好玩了,再濾濾吧!翻翻API,原來Hashtable是給每個public方法加上同步鎖當執行到table.entrySet()時它獲得了鎖,執行到foreach遍歷時已經不是調用table的方法了,已經釋放了鎖,所以也是不行的。既然如此那我就鎖對象,在每次從Map中獲取值的時候就將它鎖住,等到遍歷完成後再講鎖釋放。優化後代碼如下:

public class AsyTable {
	
	private static Map<Integer, String> table = new Hashtable<Integer, String>();
	
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println(Thread.currentThread().getName());
		new Timer().schedule(new MyTask(), 500, 500);
		int i = 1;
		while(true){
			table.put(i, "content msg" + i);
			i++;
			Thread.sleep(200);
		}
	}
	
	
	static class MyTask extends TimerTask{

		@Override
		public void run() {
			if (table.size() > 0) {
				synchronized (table) {
					Set<Entry<Integer, String>> entrySet = table.entrySet();
					for (Entry<Integer, String> entry : entrySet) {
						System.out.println(entry.getValue());
					}
					table.clear();
				}
				
			}
		}
		
	}

}

運行一下,project run perfestly.

總結:

項目中使用異步往往能提高項目運行效率,但有時候爲了數據不被髒讀取,則需要給對象加鎖。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章