背景:
在實際操作中經常會遇到一種場景,我們需要用一個成員屬性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.
總結:
項目中使用異步往往能提高項目運行效率,但有時候爲了數據不被髒讀取,則需要給對象加鎖。