Java: スレッドの制御を学ぶ(スレッドを終了させる)

スレッドを終了させる方法は、2つ。

  • mainメソッドを終了させる(プログラムと同時に起動されたスレッドの場合)
  • runメソッドを終了させる(プログラムの途中で起動されたスレッドの場合)

今の自分では、プログラムと同時にスレッドを起動させるというのがちょっとわからないのですが、全く別のプログラムを起動させるという意味と同じなんでしょうか?うーむ…。

スレッドを外部から終了させる場合は、以下のような流れです。

  1. runメソッドを実装したクラスで、スレッドの実行状態を表現するboolean型メンバ変数を持つ
  2. runメソッド内で、スレッドの実行状態を確認する仕組みを設け、実行状態が「終了」となっていたら、runメソッドが終わるようにしておく
  3. そのスレッドの実行状態を変更できるメソッドを準備する
  4. スレッドの実行状態を「実行中」にして新しいスレッドを起動させる
  5. 外部からスレッド状態を変更するためのメソッドを呼び出す

サンプルコードは以下の通り。
スレッドが1秒毎にカウンターをインクリメントして、10になったらスレッドの実行状態をfalseに変更してrunメソッドから抜けてスレッドを終了させます。

public class CounterUp implements Runnable {
    private boolean running = true; // 実行状態を表すメンバ変数
    private int i = 0;
    private String name;

    public CounterUp(String name) {
        this.name = name;
    }

    public void run() {
        while(running) {
            try {
                Tread.sleep(1000);
            } catch (InterruptedException e){}
            System.out.println(name + " : " + i);
            i++;
        }
    }

    public void stopRunning() {
        running = false;
    }

    public static void main(String[] args) {
        CounterUp cu = new CounterUp("thread 1");
        new Thread(cu).start(); // スレッド起動
        while(true) {
            if (cu.i == 10) {
                cu.stopRunning();
                System.out.println("stop!");
                break;
            }
        }
    }
}

以上です。


Java: スレッドの制御を学ぶ(休止と中断)

スレッドの制御の勉強の続き。

スレッドの休止を行うには、sleepメソッドを使います。sleepはThreadクラスのクラスメソッドで、時間設定の方法は2種類。

  • sleep(long millis) … ミリ秒指定
  • sleep(long millis, int nanos) … ミリ秒指定とナノ秒指定

ただし、ナノ秒指定には実際のところほとんど意味がないようなので、基本的にはミリ秒指定のみでよさそうです。

注意点としては、sleepによってスレッドを休止してもロックは解放されませんので、ロック解放待ちのスレッドがあると余計に処理時間がかかるようになります。synchronizedブロック内でスレッドを休止させたい場合は、sleepメソッドの代わりにwaitメソッドを使うことも検討したほうがいいそうです。
(waitメソッドについては私は現時点でよくわかってません!)

また、sleepメソッドはクラスメソッドなので、特定のインスタンスに関連づけて呼び出すことはできません!

次に、yieldメソッドです。yieldメソッドは現在処理中のスレッドを一時休止し、他のスレッドに実行の機会を与えます。yieldメソッドもsleepメソッドと同じくクラスメソッドです。休止できるのはyieldメソッドを実行しようとしているスレッド自身だけで、特定のスレッドを指定することはできません。
yieldメソッドは他のスレッドに処理が移るかもしれないけれど、そのスレッド自身も実行可能状態のままです。
(今のところ、使うタイミングわからず…。サンプル探してみるか)

最後にinterruptメソッド。interruptメソッドはスレッドに割り込みを入れるメソッドです。割り込みを入れる事のできるスレッドは、joinやsleepメソッドの実行中によって待機中のメソッド、またはObjectクラスのwaitメソッドで待機中のメソッドです。割り込みを入れられたメソッドはjava.lang.InterruptException例外を発生し、処理を再開します。

本に載っているサンプルでは、sleepを行う場合はtry-catchを行っていて、sleep終了後の処理はInterruptExceptionのcatchブロックで処理を行っているから、このように実装するのが正しいのでしょうか?うーん…。また色々とサンプルを見て勉強しなければ、わからないことが多いです(ーー;)勘違いでした。InterruptExceptionのcatchブロックでは何も処理をしていないサンプルでした。


Java: スレッドの制御を学ぶ(優先度)

さて、昨日に引き続き、Javaのスレッドの勉強です。
昨日はスレッドの方法と排他制御でした。
今日はスレッドの他の制御について。

スレッドには優先度を付けることができるようです。
複数のスレッド間で資源が競合した際、高い優先度を持つスレッドのほうから優先して実行されます。優先度は1〜10で設定でき、10が最優先のようです。

Test test = new Test(); // runnableを実装したクラスを想定
Thread thread = new Thread(test);
int priority = thread.getPriority();
thread.setPriority(Thread.MAX_PRIORITY); // 10
thread.setPriority(Thread.MIN_PRIORITY); // 1
thread.setPriority(Thread.NORM_PRIORITY); // 5

ただ、資源の割り当て方はOSやJava仮想マシンの実装に大きく依存するらしく、Windowsではそれなりに機能するようですが、Linuxではほとんど意味をなしてないとのこと(ただし、参考にしている本は2003年の本なので、この情報は古いかもしれない)。ちゃんとしたプログラムを作る場合は、優先度の設定に依存するのではなく、自力でなんとかしたほうがよさそうです。


Java: スレッドの方法と排他制御について学ぶ

私はAndroidの開発をやっているものの、もともとPHPerなのでJavaの知識はあんまりないです。だから、ちょっと難しい処理をやろうとするとどうしたらいいのかすぐにわからなくなってしまいます。それが、Javaの問題なのか、Android SDKの問題なのか、その区別も付かない場合があります。まぁそれはしゃあないとしても、地道にJavaの勉強をしていけば、Androidの開発にも役に立つのでやっていこうかと。

ちなみに本は結構持ってます。本コレクターなもんで。
でもJavaは2冊くらい…。もう何冊か必要に応じて買おうかと。

今日はスレッドの勉強をしました。
Androidでは非同期な動作はAsyncTaskが使えるので、そちらをガンガン使っててスレッドの使い方については学んでませんでした。なんか見る本でやり方が違ってたりするので、なんかよくわからんなーと思っていたのですが、どうもやり方が2種類あるようです。

  • Threadクラスを継承するクラスを作成する
  • Runnableインターフェースを実装する

どっちでもいいのか?と思っていたのですが、runメソッドをオーバーライドするだけならば、Runnableインターフェースを利用するほうが正しいようです。Threadクラスを利用する場合は、runメソッド以外もオーバーライドしてスレッドの基本的な動作を変えたいときだけ使用することが推奨されていると。なるほど。

Threadクラスを継承したほうが簡単ですが、おそらく余計なコストが発生するんでしょうね。

また、スレッドを使うとクラスのメンバ変数の状態がアクセスされたタイミングで上書きされてしまったりするので、排他制御も必要です。その場合はsynchronizedブロック、もしくはsynchronizedメソッドを使います。

以下は、synchronizedブロックの例

private int balance = 0; // メンバ変数

public void deposit(int money) {
    synchronized(this) {
        int total = balance + money;
        balance = total;
    }
}

次に、synchronizedメソッドの例

private int balance = 0; // メンバ変数

synchronized public void deposit(int money) {
    int total = balance + money;
    balance = total;
}

メソッドの処理全体をsynchrozed(this){…}で囲むのなら、synchronizedメソッドを使えということのようです。メンバ変数の排他制御のみ行えばいい訳だけど、わかりやすさはsyncronizedメソッドのほうがいいですね。代入以外が重たい処理ならば、synchronizedブロックを使うのがよさげ。

また、メンバ変数が複数のスレッドによってアクセスされる可能性があるのだったら、最初からメンバ変数自体に排他制御の印をつけることも可能のようです。それをvolatile変数というそうです。宣言するときに付けるだけ。

volatile private int balance;

明日は、スレッドの同期について学ぼうと思います。


日記:ついっぷるくじが当選!!

今日も普通に仕事していたら(といっても特にやる事がないので最近は調べものばっかりしてますが)、Twitterでダイレクトメールが届きました。しかも、ついっぷる事務局から!?年齢確認させてほしいと言われて、なんのこっちゃ?と思っていたら、どうもついっぷるくじでお酒が当選したらしいのです!?

ついっぷるとは、biglobeが運営するWebアプリのツイッタークライアントなんですが、本家よりは使いやすいのでよく使ってます。見やすいし。ちなみにDesireではtwicca使ってます。

そのついっぷるで、毎日くじに応募できるようになっているのですが、そこでトリス ハイボール 体感セットが当選した模様。

そういえば当選者発表はUstreamでやってるらしいので、前回の当選者発表をちょっと見てみたら、ちゃんとパトラッシュさんと呼ばれていました。すごい!こういうの当たったことないので感激です。当たるもんなんやなぁ…。

早速喜びをツイッターで報告したところ、おめでとう!の声が。いいなぁ。そして飲み会やるぞと。そしてなぜかATNDが立ってしまった。なにこの展開…。まぁ楽しそうなのでいいんですがw