スレッド
- スレッドとは
- スレッドクラス(Thread)
- ランナブル(Runnable)
- エグゼキューター(Executor)
- 値を返却する(Callable)
- スレッドの終了を待つ(join())
- スレッドの優先度(getPriority()/setPriority())
- スレッドの排他制御(synchronized)
- スレッド実行権の放棄(yield())
スレッドとは
スレッド を用いることにより、ひとつのプログラム(プロセス)の中で複数の処理の流れを走らせることができます。スレッドを作成するには、Thread のサブクラスを作成する方法と、Runnable インタフェースを実装したオブジェクトを用いる方法があります。
スレッドクラス(Thread)
Thread クラスのサブクラスを作成することにより、スレッドを作成する方法を以下に示します。Thread クラスのサブクラスを定義し、そのインスタンスを生成し、start() メソッドを呼び出すことでサブクラスの run() メソッドが実行されます。
class Main {
public static void main(String[] args) {
ThreadTest tt = new ThreadTest();
tt.start();
for (int i = 0; i < 1000; i++) {
System.out.print('.');
}
}
}
class ThreadTest extends Thread {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.print('o');
}
}
}
ランナブル(Runnable)
すでに他のクラスを継承しているサブクラスをスレッド化したい場合は、Thread クラスのサブクラスとして定義するのではなく、代わりに Runnable インタフェースを実装させます。このクラスのインスタンスを引数にして Thread クラスのインタフェースを作成し、start() メソッドを呼び出します。
class Main {
public static void main(String[] args) {
RunnableTest tt = new RunnableTest();
Thread t = new Thread(tt);
t.start();
for (int i = 0; i < 1000; i++) {
System.out.print('.');
}
}
}
class RunnableTest implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.print('o');
}
}
}
エグゼキューター(Executor)
newFixedThreadPool()
Executor を用いると、スレッドの生成・削除処理は重いので、あらかじめ必要個数のスレッドをワーカーとして起動しておき、空いているワーカーに処理を割り当てることができます。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Main {
public static void main(String[] args) {
ExecutorService es = null;
try {
es = Executors.newFixedThreadPool(3);
for (int i = 0; i < 9; i++) {
es.execute(new RunnableTest(i));
}
} finally {
es.shutdown();
}
}
}
class RunnableTest implements Runnable {
int n;
RunnableTest(int n) {
this.n = n;
}
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.print(n);
}
}
}
newSingleThreadExecutor()
ワーカーがひとつの時は newFixedThreadPool() の代わりに newSingleThreadExecutor() を使用できます。
es = Executors.newSingleThreadExecutor();
newCachedThreadPool()
newCachedThreadPool() を用いると、処理を行うのに必要な個数のワーカーを自動的に起動し、60秒間使用されなければ消滅させることができます。下記の例では、最初に5個のワーカーを生成し、5秒後には同じワーカーを使用しますが、60秒後にはすべて一度削除され、65秒後には新たなワーカーが5個生成されます。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Main {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ExecutorService es = null;
try {
es = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
es.execute(new RunnableTest());
}
Thread.sleep(5 * 1000);
System.out.println("----5秒後");
for (int i = 0; i < 5; i++) {
es.execute(new RunnableTest());
}
Thread.sleep(65 * 1000);
System.out.println("----65秒後");
for (int i = 0; i < 5; i++) {
es.execute(new RunnableTest());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
es.shutdown();
}
}
}
class RunnableTest implements Runnable {
public void run() {
System.out.println("[" + Thread.currentThread().threadId() + "]");
}
}
値を返却する(Callable)
値を返却するスレッドを作成するには Callable インタフェースを実装するクラスを作成し、execute() の代わりに submit() を使用します。結果は Future<返却型> で返され get() で値を得ます。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class Main {
public static void main(String[] args) {
ExecutorService es = null;
try {
es = Executors.newSingleThreadExecutor();
@SuppressWarnings("unchecked")
Future<Long> future = es.submit(new CallableTest());
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
es.shutdown();
}
}
}
class CallableTest implements Callable {
public Long call() {
return Thread.currentThread().threadId();
}
}
スレッドの終了を待つ(join())
スレッドの終了を待つには join() を用います。
class Main {
public static void main(String[] args) {
try {
ThreadTest tt = new ThreadTest();
tt.start();
System.out.println("Waiting...");
tt.join();
System.out.println("Finished...");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ThreadTest extends Thread {
public void run() {
try {
Thread.sleep(3 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
スレッドの優先度(getPriority()/setPriority())
スレッドの優先度を得るには getPriority()、設定するには setPriority() を使用します。
class Main {
public static void main(String[] args) {
Thread t1 = new ThreadTest("1");
Thread t2 = new ThreadTest("2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
class ThreadTest extends Thread {
String s;
ThreadTest(String s) {
this.s = s;
}
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.print(this.s);
}
}
}
スレッドの排他制御(synchronized)
複数のスレッドが同時に処理を行ってしまうとまずい場合があります。例えば下記の例では、1000個のスレッドを実行し、それぞれがカウンターをひとつずつカウントアップするので結果は 1000になるはずですが、結果は 1000 よりも少ない値になったりします。これは、カウンター値を読み出して設定するまでの間に他のスレッドが割り込んでしまい、同じ値を読み出して同じ値を設定してしまうために発生します。
class Main {
static Counter counter = new Counter();
public static void main(String[] args) {
// スレッドを1000個作成する
MyThread[] threads = new MyThread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new MyThread();
threads[i].start();
}
// スレッドがすべて終了するのを待つ
for (int i = 0; i < 1000; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// カウンターを表示する
System.out.println(Main.counter.count);
}
}
class MyThread extends Thread {
public void run() {
Main.counter.countUp();
}
}
class Counter {
int count;
void countUp() {
System.out.print("[");
int n = count; // カウンターを読み出して
System.out.print(".");
count = n + 1; // 加算して書き戻す
System.out.print("]");
}
}
下記に実行結果の例を示します。[ と ] の対応がとれていない部分が、複数のスレッドが処理を同時実行してしまっている部分です。
[.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][[[[[[[[[........]]]]]]]].][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.]983
この問題を防ぐには、同期処理 や 排他制御 と呼ばれる制御を行います。下記のように synchronized を用いることで、(...) で指定したオブジェクト(下記の例では this、つまり Counter オブジェクト)に対してロック権を取得した単一のスレッドのみが { ... } の処理を実行できるようになります。
void countUp() {
synchronized (this) {
System.out.print("[");
int n = count; // カウンターを読み出して
System.out.print(".");
count = n + 1; // 加算して書き戻す
System.out.print("]");
}
}
上記は、下記のように書き表すこともできます。
synchronized void countUp() {
System.out.print("[");
int n = count; // カウンターを読み出して
System.out.print(".");
count = n + 1; // 加算して書き戻す
System.out.print("]");
}
これにより、起動したスレッドの数だけ正常にカウントを行うことができるようになります。
[.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.][.] [.][.][.][.][.][.][.][.][.][.][.][.][.]1000
synchronized メソッドは、常にひとつのスレッドのみがそのメソッドを実行すると誤解されがちですが、スレッドを実行するインスタンスが複数あれば、インスタンスの個数だけ多重に実行される可能性がありますので注意してください。例えば、上記の例で countUp() ではなく run() メソッドを synchronized にしても、run() メソッドのインスタンスはスレッドの個数分 1000個ありますので、排他制御はうまく機能しません。
synchronized public void run() { // 駄目な例
SyncTest.counter.countUp();
}
グローバルに参照可能なロック用オブジェクトを作成しておき、排他制御を行いたい箇所で下記のように使用する方法もあります。
class Global {
static Object lock = new Object();
}
class Counter {
void countUp() {
synchronized (Global.lock) {
// 排他制御を行いたい箇所
}
}
}
スレッド実行権の放棄(yield())
yield() はスレッドの実行権を一度放棄し、他のスレッドを優先的に実行します。下記の例では ThreadTestA の方が yield() により他のスレッドに処理を譲っているため、ThreadTestA の方が完了が遅くなります。
class ThreadTestA extends Thread {
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.print(".");
Thread.yield();
}
}
}
class ThreadTestB extends Thread {
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.print("o");
}
}
}
class Main {
public static void main(String args[]) {
ThreadTestA thA = new ThreadTestA();
ThreadTestB thB = new ThreadTestB();
thA.start();
thB.start();
}
}