タイマーはJDKの初期APIです。私たちは通常、TimerとTimertaskを使用して、News -CheduledThreadPoolが出る前に遅延と周期性のあるタスクを行いますが、タイマーにはいくつかの欠陥があります。なぜ私たちはそう言うのですか?
タイマーは、すべてのタイマータスクを実行するための一意のスレッドのみを作成します。タイマータスクの実行が時間がかかる場合、他のティマタスクの精度に問題が発生します。たとえば、1つのTimertaskが10秒ごとに実行され、もう1つのTimertaskは40ミリ秒ごとに実行されます。繰り返されるタスクは、後続のタスクが完了した後、4回連続で呼び出されます。タイマーのもう1つの問題は、TimerTaskが未確認の例外をスローする場合、タイマースレッドを終了することです。この場合、タイマーはスレッドの実行に再び返信しません。タイマー全体がキャンセルされたと誤って信じています。スケジュールされているがまだ実行されていないTimertaskは、再び実行されることはなく、新しいタスクをスケジュールすることはできません。
ここで、問題を再現するために小さなデモを作成しました。コードは次のとおりです。
パッケージcom.hjc; import java.util.timer; Import java.util.timertask;/*** 2018/7/12にCONGによって作成されました。 */public class timertest {// Timer Object Static Timer Timer = new Timer(); public static void main(string [] args){//タスク1の追加、execution timer.schedule(new timertask(){@override public void run(){system.out.println( "-1つのタスク---"); try {thread.sleep(1000);} catch(interruptedexception e){e.printstacktrace( "e.printexception(" e.printexception) ");}}、500); //タスク2を追加し、実行temer.schedule(new timertask(){@override public void run(){for(;;){for(;;){system.out.println( " - 2タスク---"); try {thread.sleep(1000);} catch(interruptedexception e){// dodo auto-generated catch e.printstoctacktacktack( }、1000); }}上記のように、最初にタスクを追加して500ms後に実行し、次に2番目のタスクを追加して1秒後に実行しました。私たちが期待するのは、最初のタスクが1つのタスクを出力し、1を待つとき、2番目のタスクが出力される - 2つのタスク---、
ただし、コードを実行した後、出力は次のとおりです。
例2、
public class shedule {private static long Start; public static void main(string [] args){timertask task = new timertask(){public void run(){system.out.println(system.currenttimemillis() - start); {thread.sleep(3000); } catch(arturnedexception e){e.printstacktrace(); }}}; timertask task1 = new Timertask(){@Override public void run(){system.out.println(system.currenttimemillis() - start); }};タイマータイマー= new Timer(); start = system.currenttimemillis(); //スケジュールされたタスクを起動し、Timer.schedule(タスク、1000)を実行します。 //スケジュールされたタスクを起動し、Timer.schedule(task1,3000)を実行します。 }}上記のプログラムは、2番目のタスクが3秒、つまり100と1つの3000を出力した後に最初のタスクが実行された後に実行されると予想されます。
実際の操作結果は次のとおりです。
実際の操作結果は、私たちが望む通りではありません。世界の結果は、2番目のタスクは4秒後に出力されます。つまり、4001は約4秒です。その部分はどこに行きましたか?その時は私たちの最初の仕事の睡眠によって占められていました。
次に、最初のタスクでthread.sleep()を削除します。このコードラインは正しく実行されていますか?操作結果は次のとおりです。
最初のタスクは1秒後に実行され、2番目のタスクは最初のタスクが実行されてから3秒後に実行されることがわかります。
これは、タイマーがすべてのタイマータスクを実行するための一意のスレッドのみを作成することを意味します。タイマータスクの実行が時間がかかる場合、他のティマタスクの精度に問題が発生します。
タイマーの実装原則分析
以下は、タイマーの原則の簡単な紹介です。次の図は、タイマーの原理モデルの紹介です。
1.Taskqueueは、バランスの取れたバイナリツリーヒープによって実装される優先キューであり、各タイマーオブジェクトには内部に一意のタスクキューキューがあります。ユーザースレッド呼び出しタイマーのスケジュール方法は、TimerTaskタスクをTaskqueueキューに追加することです。スケジュールメソッドを呼び出すと、長い遅延パラメーターを使用して、タスクが実行されるまで遅延しているかを示します。
2。TimerThreadは、特定のタスクを実行するスレッドです。実行のためのタスクキューキューから最優先事項のタスクを取得します。現在のタスクが実行された後にのみ、次のタスクがキューから取得されることに注意する必要があります。キューに設定された遅延時間があるかどうかに関係なく、タイマーにはタイマースレッドスレッドが1つしかないため、タイマーの内部実装はマルチプロデューサーの単一消費者モデルであることがわかります。
実装モデルから、上記の問題を調査するには、TimerThreadの実装を検討するだけであることがわかります。 TimerThreadの実行方法の主な論理ソースコードは次のとおりです。
public void run(){try {mainloop(); }最後に{//誰かがこのスレッドを殺し、まるでタイマーが同期(queue)をキャンセルしたかのように振る舞いました{newtasksmaybescheduled = false; queue.clear(); //時代遅れの参照を排除}}}} private void mainloop(){while(true){try {timertask task; Boolean Taskfired; // lock synchronized(queue){......} if(taskfired)task.run(); // execute task} catch(furtredexception e){}}}タスクの実行中に中断されたエクセプト以外の例外がスローされると、例外をスローするために唯一の消費者スレッドが終了し、キューで実行される他のタスクがクリアされることがわかります。したがって、Timertaskの実行方法で主な可能な例外をキャッチするために、トライキャッチ構造を使用して、実行方法以外で例外をスローしないことが最善です。
実際、タイマーと同様の関数を実装するには、ScheduledThreadPoolexecutorのスケジュールを使用する方が良い選択です。 ScheduledThreadPoolexecutorの1つのタスクが例外をスローし、他のタスクは影響を受けません。
ScheduledThreadPoolexecutorの例は次のとおりです。
/*** 2018/7/12にCONGによって作成されました。 */public class scheduledthreadpoolexecutortest {static scheduledthreadpoolexecutor scheduledthreadpoolexecutor = new ScheduleDthreadPoolexecutor(1); public static void main(string [] args){scheduledthreadpoolexecutor.schedule(new runnable(){public void run(){system.out.println( " - one task ---」) }、500、timeunit.microseconds); scheduledthreadpoolexecutor.schedule(new runnable(){public void run(){for(int i = 0; i <5; ++ i){system.out.println( " - 2タスク---」) timeUnit.microSeconds); ScheduleDthreadPoolexecutor.shutdown(); }}操作結果は次のとおりです。
スケジュールされたThreadPoolexecutorの他のタスクが例外をスローするタスクの影響を受けない理由は、CATCHがスケジュールされたThreadPoolexecutorのスケジュールされたFutureTaskタスクの例外をドロップするためですが、スレッドプールタスクの実行方法で例外をキャッチし、実行ログを印刷するためにキャッチを使用するためにキャッチを使用することをベストプラクティスです。