結城先生のデザパタ本マルチスレッド編がサクッと読めそうだったので読み進めているので、自分用メモ。ほとんど知っている内容ではあったけど、体系的に知識が入っていなかったので、よい確認になる...。
スレッドを起動する方法は 2 つある
Thread
クラスのサブクラスのインスタンスを使ってスレッドを起動するRunnable
インターフェースの実装クラスのインスタンスを使ってスレッドを起動するclass ThreadSubclass extends Thread { private final Logger logger = LoggerFactory.getLogger(this.getClass()); public void run() { IntStream.range(0, 10).forEach(i -> logger.info(Integer.toString(i))); } } class RunnableImpl implements Runnable { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void run() { IntStream.range(0, 10).forEach(i -> logger.info(Integer.toString(i))); } }
それぞれ run()
メソッドを実装している。これらを直接呼び出した場合、呼び出したスレッドで処理が実行される。別のスレッドで実行させたい場合は、start()
を呼び出す必要がある。以下のテストを実行すると、run()
の呼び出しはメインスレッドから行なわれているのが観測できる。
@Test public void threadSubclassTest() { final ThreadSubclass threadSubclass = new ThreadSubclass(); threadSubclass.run(); threadSubclass.start(); try { threadSubclass.join(); } catch (InterruptedException e) { e.printStackTrace(); } } @Test public void runnableImplTest() { final RunnableImpl runnable = new RunnableImpl(); final Thread thread = new Thread(runnable); thread.run(); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } }
スレッドの生成には ThreadFactory
なるものを使うこともできる。これにより Thread
を new
せずに済む。
@Test public void runnableImplTestWithThreadFacotory() { final ThreadFactory factory = Executors.defaultThreadFactory(); final Thread thread = factory.newThread(new RunnableImpl()); thread.run(); thread.start(); try { thread.join(); } catch (InterruptedException e) { LoggerFactory.getLogger(this.getClass()).error("interrupted", e); } }
synchronized
メソッドを使うsynchronized
ブロックを使うあるスレッドから synchronized インスタンスメソッドが呼ばれている間は、 他のスレッドは同じインスタンスの synchronized メソッドを呼び出すことができない。 非 synchronized インスタンスメソッドについては問題なく呼び出すことができる。 ロックはインスタンスごとにされる点に注意。
次の synchronized メソッド と synchronized ブロックを使ったメソッドは等価になります。
class TestClass { synchronized void methodWithSynchronized() { // do something } void methodWithSynchronizedBlock() { synchronized (this) { // do something } } }
また、次の synchronized クラスメソッドと synchronized ブロックを使ったクラスメソッドは等価になります。
class TestClass { static synchronized void staticMethodWithSynchronized() { // do something } static void staticMethodWithSynchronizedBlock() { synchronized (TestClass.class) { //do something } } }
スレッドの排他制御をする仕組みを モニタ と呼びます。 また、ロックをとっていることをモニタを所有する、あるいはロックをホールドすると呼ぶこともあります。
各インスタンスはウェイトセットという仮想的な概念を持っています。
あるインスタンスがあるスレッドに wait
メソッドを呼び出されたとき、インスタンスのウェイトセットにそのスレッドが追加されます。
スレッドは notify
, notifyAll
, interrupt
の呼び出しが発生するか wait
のタイムアウトが発生するまで停止します。
synchronized 文やブロックの外側で オブジェクトの wait を呼び出すと IllegalMonitorStateException
が発生がスローされます。
private final Object obj = new Object(); private void testMethod() throws Exception { final Thread thread = new Thread(() -> { try { Thread.sleep(1000); } catch (final Exception e) { e.printStackTrace(); } synchronized (obj) { obj.notify(); // obj のウェイトセットに入ってるスレッドを1つだけ起こす } }); thread.start(); synchronized (obj) { obj.wait(); // main thread が obj の ウェイトセットに入る } }
スレッドには処理をキャンセルする機能があったり、優先度を設定できたり、スレッドの終了待ちをできたりもする。
うち安全性と生存性を守るのは必須
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです