[java]ロックしないと怒ります。 2009/02/23

Javaです。

いまだによく理解できないものに同期処理があります。

まずは、synchronized処理から。
単純にふたつのスレッドから、StringBuilderに連続して文字を書き込みたいということを想定してみます。
ひとつが完全に処理を終わらないと、片一方は処理してはいけないという仕様です。
synchronizedブロックを使って実現してみます。
もちろん、両方でsynchronizedブロックを使わないとてんでばらばらな書き込みになってしまいます。

package th;

public class TestA {

public static void main(String[] args) throws InterruptedException {

final StringBuilder builder = new StringBuilder();

Thread thread = new Thread(new Runnable() {
public void run() {
synchronized (builder) {
for (int i = 0; i < 100; i++) {
builder.append("A" + i).append("\n");
sleep(10);
}
}
}
});

Thread thread2 = new Thread(new Runnable() {
public void run() {
synchronized (builder) {
for (int i = 0; i < 100; i++) {
builder.append("B" + i).append("\n");
sleep(10);
}
}
}
});

thread.start();
thread2.start();

thread.join();
thread2.join();
System.out.println(builder);
}

static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static class A {
StringBuilder builder = new StringBuilder();
}

}


つぎに、Javaの標準パッケージにあるjava.util.concurrent.locks.Lockを使ってみます。
下のような使い方をしてみました。

package th;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestB {

public static void main(String[] args) throws InterruptedException {

class Writer {
final StringBuilder builder = new StringBuilder();
final Lock lock = new ReentrantLock();

void lock() {
this.lock.lock();
}

void unlock() {
this.lock.unlock();
}

void writer(String s) {
builder.append(s);
}

public String toString() {
return builder.toString();
}
}

final Writer writer = new Writer();

Thread thread = new Thread(new Runnable() {
public void run() {
writer.lock();
try {
for (int i = 0; i < 100; i++) {
writer.writer("A" + i + "\n");
sleep(10);
}
} finally {
writer.unlock();
}

}
});

Thread thread2 = new Thread(new Runnable() {
public void run() {
writer.lock();
try {
for (int i = 0; i < 100; i++) {
writer.writer("B" + i + "\n");
sleep(10);
}
} finally {
writer.unlock();
}
}
});

thread.start();
thread2.start();

thread.join();
thread2.join();
System.out.println(writer);
}

static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static class A {
StringBuilder builder = new StringBuilder();
}

}


lockとunlockの組み合わせがsynchronizedブロックよりわかりやすいかなと、個人的には思います。

ただ、lockをしないで使うと、やはりばらばらな書き込みができてしまいます。

次に、lockしないと怒られる(実行時エラーを吐く)というふうにしてみます。
標準パッケージにjava.util.concurrent.locks.ReentrantLockというのがありまして、このクラスは、isHeldByCurrentThreadメソッドを持ちます。

使われるときにisHeldByCurrentThreadで現在のスレッドが、lockしてない場合は、例外を吐くというふうにしています。

package th;

import java.util.concurrent.locks.ReentrantLock;

public class TestC {

public static void main(String[] args) throws InterruptedException {

class Writer {
final StringBuilder builder = new StringBuilder();
final ReentrantLock lock = new ReentrantLock();

void lock() {
this.lock.lock();
}

void unlock() {
this.lock.unlock();
}

void writer(String s) {
if (!lock.isHeldByCurrentThread()) {
throw new RuntimeException("***");
}

builder.append(s);
}

public String toString() {
return builder.toString();
}
}

final Writer writer = new Writer();

Thread thread = new Thread(new Runnable() {
public void run() {
writer.lock();
try {
for (int i = 0; i < 100; i++) {
writer.writer("A" + i + "\n");
sleep(10);
}
} finally {
writer.unlock();
}

}
});

Thread thread2 = new Thread(new Runnable() {
public void run() {
// writer.lock();
/*
* lockしてないので実行時エラーがでます。
*/
try {
for (int i = 0; i < 100; i++) {
writer.writer("B" + i + "\n");
sleep(10);
}
} finally {
// writer.unlock();
}
}
});

thread.start();
thread2.start();

thread.join();
thread2.join();
System.out.println(writer);
}

static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static class A {
StringBuilder builder = new StringBuilder();
}

}



最後にsynchronizedブロックで似たようなことを実現してみます。

現在のスレッドがこのロックを保持しているかどうかを照会するにはThread.holdsLock(java.lang.Object) メソッドを使います。

下記のようなコードにしてみました。

package th;

public class TestD {

public static void main(String[] args) throws InterruptedException {

class Writer {
final StringBuilder builder = new StringBuilder();

void writer(String s) {
if (!Thread.holdsLock(this)) {
throw new RuntimeException("***");
}
builder.append(s);
}

public String toString() {
return builder.toString();
}
}

final Writer writer = new Writer();

Thread thread = new Thread(new Runnable() {
public void run() {

synchronized (writer) {
for (int i = 0; i < 100; i++) {
writer.writer("A" + i + "\n");
sleep(10);
}
}
}
});

Thread thread2 = new Thread(new Runnable() {
public void run() {

/*
* writerをsynchronizedしていないので例外が発生します。
*/
for (int i = 0; i < 100; i++) {
writer.writer("B" + i + "\n");
sleep(10);
}

}
});

thread.start();
thread2.start();

thread.join();
thread2.join();
System.out.println(writer);
}

static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

static class A {
StringBuilder builder = new StringBuilder();
}

}

: