記一次線程Timer導致的線程安全問題修正


接收別人的項目別人的項目,發現了一段很誇張的代碼,居然用源碼的方式實現websocket……
還單獨開了一個端口,多線程websocket,在調用Service的服務,定時執行什么的。
繞了好半天沒有緩過勁來,不過自己debug的時候,沒發現什么問題,就想着隨它去吧。
結果過幾天,報出了以下問題。

java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
	.....

好吧,這是多線程訪問HashMap導致的線程不安全問題。想改,但是單純看實在看不出來。於是把原本代碼,簡化簡化,做了個debug用項目出來。

package error_test_001;
import java.util.*;

public class error_test {
	public static void main(String[] arge) throws InterruptedException {
		Test ttmtransService = new Test();
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				ttmtransService.StartTask();
			}
		};
        Thread thread[] = new Thread[100];
        for (int i = 0; i < thread.length; i++) {
            thread[i] = new Thread(runnable);
            thread[i].start();
        }
	}
}
class Test{
	/**返回結果數據**/
	private Map<String,Object> ResultData=new HashMap<String,Object>();
	/**定時器**/
	public static Timer TaskTimer;
	/**查詢任務**/
	private TaskQuery mytask;

	/**開始執行任務**/
	public synchronized void StartTask(){
		if(TaskTimer != null){
			CloseTimer();
		}
		TaskTimer=new Timer();
		mytask=new TaskQuery();
		TaskTimer.schedule(mytask, new Date(),1000);
	}

	/**
	 * 關閉定時器
	 */
	private synchronized void CloseTimer(){
		TaskTimer.cancel();
		TaskTimer.purge();
		TaskTimer = null;
	}

	public class TaskQuery extends TimerTask{
		/** 原來時間戳 **/
		private long time;
		/** 現在時間戳 **/
		private long now;
		/** 執行中flg **/
		private boolean flgRunning = false;
		public void run(){
			if (flgRunning){
				return;
			}
			flgRunning = true;
			try {
				System.out.print("開始執行...");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("結束執行...");
			}finally {
				// 結束時,flag
				flgRunning = false;
			}
		}
	}
}

上邊的項目執行:

開始執行...開始執行...開始執行...結束執行...
結束執行...
結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...

好吧,至少確定有線程問題了。

其實,代碼已經簡化很多了,正常的一個Timer的話,是【task執行->task執行完畢->定時等待->task執行->task執行完畢->定時等待】,這樣依次執行的,至少個人debug,在類似單線程環境下基本沒為什么問題。
於是再仔細考慮了下代碼,果然,問題出在Timer和TaskQuery的創建上呢,每次new對象,於是和之前的沒關系,相互之間各自執行了。於是把定時器的開始和關閉方法StartTask和CloseTimer中的內容,改為全新建,全加的方法

package error_test_001;
import java.util.*;

public class error_test {
	public static void main(String[] arge) throws InterruptedException {
		Test ttmtransService = new Test();
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
					ttmtransService.StartTask();

					Thread.sleep(5000);
					ttmtransService.CloseTimer();
					ttmtransService.StartTask();

					Thread.sleep(5000);
					ttmtransService.CloseTimer();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
        Thread thread[] = new Thread[100];
        for (int i = 0; i < thread.length; i++) {
            thread[i] = new Thread(runnable);
            thread[i].start();
        }
	}
}
class Test{
	/**返回結果數據**/
	private Map<String,Object> ResultData=new HashMap<String,Object>();
	/**定時器**/
	public static Timer TaskTimer;
	/**查詢任務**/
	private TaskQuery mytask;

	/**開始執行任務**/
	public synchronized void StartTask(){
		if(TaskTimer == null){
			System.out.print("StartTask...");
			TaskTimer=new Timer();
			mytask=new TaskQuery();
			TaskTimer.schedule(mytask, new Date(),1000);
		}
	}

	/**
	 * 關閉定時器
	 */
	public synchronized void CloseTimer(){
		if(TaskTimer != null){
			System.out.print("CloseTimer...");
			TaskTimer.cancel();
			TaskTimer.purge();
			TaskTimer = null;
		}
	}

	public class TaskQuery extends TimerTask{
		/** 原來時間戳 **/
		private long time;
		/** 現在時間戳 **/
		private long now;
		/** 執行中flg **/
		private boolean flgRunning = false;
		public void run(){
			if (flgRunning){
				return;
			}
			flgRunning = true;
			try {
				System.out.print("開始執行...");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("結束執行...");
			}finally {
				// 結束時,flag
				flgRunning = false;
			}
		}
	}
}

然后看看結果:

StartTask...開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...開始執行...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...開始執行...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...開始執行...結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...CloseTimer...結束執行...

嗯……開始是沒錯,但是中間嘗試多線程停止timer並重新開始的時候出問題了。反復的開始關閉交錯執行,每次都會新開區一個TaskQuery,導致有很多tark新執行。而這些TaskQuery都是new出來的,於是使用synchronized也無效。

  1. Timer的TaskQuery雖然取消,但是TaskQuery也不能執行到一半強制中斷。所以繼續執行時1. 正常的。
  2. 然后,new出來的TaskQuery,在一次使用后,不能再次放入Timer中,所以new是正常的。
  3. 然后Timer沒發判斷內部的執行狀態,沒想做延時也不行。

好吧,然后又是看代碼……說起來,里邊有個flgRunning呢,定義在內部是沒什么用的,那么拿出來試試?

package error_test_001;
import java.util.*;

public class error_test {
	public static void main(String[] arge) throws InterruptedException {
		Test ttmtransService = new Test();
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
					ttmtransService.StartTask();

					Thread.sleep(5000);
					ttmtransService.CloseTimer();
					ttmtransService.StartTask();

					Thread.sleep(10000);
					ttmtransService.CloseTimer();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
        Thread thread[] = new Thread[100];
        for (int i = 0; i < thread.length; i++) {
            thread[i] = new Thread(runnable);
            thread[i].start();
        }
	}
}
class Test{
	/**返回結果數據**/
	private Map<String,Object> ResultData=new HashMap<String,Object>();
	/**定時器**/
	public static Timer TaskTimer;
	/**查詢任務**/
	private TaskQuery mytask;
	/** 執行中flg **/
	private boolean flgRunning = false;

	/**開始執行任務**/
	public synchronized void StartTask(){
		if(TaskTimer == null){
			System.out.print("StartTask...");
			TaskTimer=new Timer();
			mytask=new TaskQuery();
			TaskTimer.schedule(mytask, new Date(),1000);
		}
	}

	/**
	 * 關閉定時器
	 */
	public synchronized void CloseTimer(){
		if(TaskTimer != null){
			System.out.print("CloseTimer...");
			TaskTimer.cancel();
			TaskTimer.purge();
			TaskTimer = null;
		}
	}

	public class TaskQuery extends TimerTask{
		/** 原來時間戳 **/
		private long time;
		/** 現在時間戳 **/
		private long now;
		public void run(){
			if (flgRunning){
				return;
			}
			flgRunning = true;
			try {
				System.out.print("開始執行...");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("結束執行...");
			}finally {
				// 結束時,flag
				flgRunning = false;
			}
		}
	}
}

結果:

StartTask...開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...結束執行...
CloseTimer...StartTask...CloseTimer...StartTask...開始執行...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...CloseTimer...StartTask...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...結束執行...
開始執行...CloseTimer...結束執行...

好吧,可以接受。這算是手動的加了個鎖。能解決問題就好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM