著者:LittleMagic
出典: 注のSystem.currentTimeMillis()の潜在的なパフォーマンスの問題
出典:ジェーンの本
パブリック クラスCurrentTimeMillisPerfDemo { プライベート 静的 最終 int型の COUNT = 100 ; パブリック 静的 ボイドメイン(文字列[]引数)をスロー例外{ 長い BEGINTIME = System.nanoTimeのを(); 以下のために(INT I 0 =; I <COUNT; I ++ ){ にSystem.currentTimeMillis(); } 長い経過時間= System.nanoTimeの() - BEGINTIME。 System.out.println( "100件のSystem.currentTimeMillis()シリアルコール:" +経過時間+ "NS" )。 たCountDownLatch startLatch = 新しいたCountDownLatch(1 )。 たCountDownLatch endLatch = 新たCountDownLatch(COUNT); 以下のために(int型私は= 0; I <COUNT; I ++ ){ 新しいスレッド(() - > { 試み{ startLatch.await(); のSystem.currentTimeMillis(); } キャッチ(InterruptedExceptionある電子){ e.printStackTrace(); } 最後に{ endLatch.countDown()。 } }))(始めます。 } BEGINTIME = System.nanoTimeの()。 startLatch.countDown(); endLatch.await(); 経過時間 = System.nanoTimeの() - BEGINTIME。 System.out.println( "100件のSystem.currentTimeMillis()平行コール:" +経過時間+ "NS" )。 } }
図の実行結果は次の通りです。
目に見える、同時呼び出しのSystem.currentTimeMillis()百倍、費やした時間は250倍百倍のシングルスレッドです。コールが(例えば、ミリ秒ごとに数回のポイントに)シングルスレッドの周波数を増加させる場合も同様の状況を観察しました。実際には、極端な場合には、にSystem.currentTimeMillis()時間がかかり、よりも、単純なオブジェクトのインスタンスを作成するよりも、>のように試してみてください<新しいHashMapのに上記の文では、独自のスレッドことができるかを教えてください。
なぜこのようにですか?System.currentTimeMillis()ネイティブ実装でjavaTimeMillis()メソッドは、ある、ホットスポット/ SRC / OS / Linuxの/ VM / os_linux.cppファイルホットスポット・ソースに来ました。
jlong OS :: javaTimeMillis(){ 体timeval 時間。 int型のステータス=はgettimeofday(&時間、NULL); アサート(ステータス = - !1、" Linuxのエラー" ); 復帰jlong(時間 .tv_sec)* 1000年 + jlong(時間 .tv_usec / 1000年); }
外国人の大物が深い探求するレベルの編集にすでに存在しているので、詳細はで見つけることができ、ここで停止したソースを掘る「)(スローにcurrentTimeMillis」の記事は、私は不法侵入しません。簡単に言えば、それはこれです:
- カーネルモードへのユーザモードから切り替える必要)(gettimeofdayのを呼び出します。
- gettimeofday(特に貧弱な性能HPETタイマーでLinuxシステムタイマ(クロック源)の影響によって)性能。
- 唯一のグローバル・システム・クロック・ソース、高い同時アクセスは深刻なまたは頻繁競合を引き起こす可能性があります。
その理由は、パフォーマンスの低下のHPETタイマー意志時間はシリアル実行のためのすべての要求をスタンプです。より良いパフォーマンスタイマーTSC、特殊レジスタは、タイムスタンプを保存するため。欠点は、(CLK信号処理に関連する)、純粋なハードウェアタイマ、可変周波数であるため、それが不安定であってもよいです。詳細についてはHPETとTSCを見つけることができますhttps://en.wikipedia.org/wiki/High_Precision_Event_Timerとhttps://en.wikipedia.org/wiki/Time_Stamp_Counter。
さらに、次のコマンドは、クロック・ソースを表示および変更するために使用することができます
〜猫 / SYS /デバイス/システム/クロックソース/ clocksource0 / available_clocksource TSC HPET acpi_pm 〜猫 / SYS /デバイス/システム/クロックソース/ clocksource0 / current_clocksource TSC 〜エコー ' HPET ' > / SYS /デバイス/システム/クロックソース/ clocksource0 / current_clocksource
どのようにこの問題を解決するには?最も一般的な方法は、することですグローバルキャッシュを維持するために同等のミリ秒単位のタイムスタンプを更新するために、単一のスケジュールスレッドを使用します。他のスレッドが取られたときにメモリからタイムスタンプをとることに相当し、クロックとのリソースの競合は発生しません、費用はいくつかの精度を犠牲にしています。次のように特定のコード。
パブリック クラスCurrentTimeMillisClock { プライベート 揮発性の 長い今、 プライベートCurrentTimeMillisClock(){ この .now = にSystem.currentTimeMillis(); scheduleTick(); } プライベート ボイドscheduleTick(){ 新規のScheduledThreadPoolExecutor(1、実行可能- > { スレッドスレッド = 新しいスレッド(実行可能、 "現時間ミリ秒" ); thread.setDaemon(真); 戻り糸; })。scheduleAtFixedRate(() - >{ 今 = にSystem.currentTimeMillis(); }、 1、1 、TimeUnit.MILLISECONDS)。 } パブリック ロング今(){ 戻り今。 } パブリック 静的CurrentTimeMillisClockのgetInstance(){ 戻りSingletonHolder.INSTANCE。 } プライベート 静的 クラスSingletonHolder { プライベート 静的 最終 CurrentTimeMillisClockのINSTANCE = 新しいCurrentTimeMillisClock()。 } }
CurrentTimeMillisClock.getInstance().now()
、それに。しかし、にSystem.currentTimeMillis()はプログラムの全体的な効率の効率に影響を与えていない場合、あなたは最適化をやって忙しくない、これは極端な状況のためだけの準備です。