Slide 1

Slide 1 text

Java並行処理の基本 2020/5/29 今泉俊幸 1

Slide 2

Slide 2 text

目次 • 並行処理とは • プロセスとスレッド • Javaで並行処理を行う • Threadを使った実装 • 並行処理を行う際の注意点 • スレッドセーフ • synchronized • スレッドセーフなクラス • デッドロック • 実践的なマルチスレッドクラス • Executor Framework、Fork/Join 2

Slide 3

Slide 3 text

並行処理とは • 複数の処理を論理的に同時に実行すること • 物理的に同時に実行できる処理数は限りがある(CPUのコア数に依存) • 複数の処理を高速で切り替えて実行し、同時にやっているように見える • 複数の処理を物理的に同時に行うことは「並列処理」と呼ばれる • 並行処理を実現するために「プロセス」と「スレッド」という概念がある • プロセス • OSから見える処理の単位 • 他のプロセスとメモリ空間を共有しない • スレッド • プロセスの中で並行処理を行う仕組み • 他のスレッドとメモリ空間を共有する 3

Slide 4

Slide 4 text

• ローカル変数は各スレッド毎に保持されるが、それ以外の値は共有 • 複数のスレッドから同じ変数の値を書きかえる場合は注意が必要(後述) perspective=java workspace= C:¥workspace … プロセス、スレッドのイメージ図(1/2) プロセス(例:Eclipse) メモリ スレッドA(ビルド) src=/src jdk=11 スレッドB(静的解析1) src=hoge.java lineNum=100 complexity=5 スレッドD(描画) fps=60 スレッドC(静的解析2) src=piyo.java lineNum=20 complexity=1 4

Slide 5

Slide 5 text

HOGE=hoge プロセス、スレッドのイメージ図(2/2) メモリ サンプルクラス public class ClassA{ static String HOGE = “hoge”; private int count; public void add(int n){ int adder = n * n; count += adder; } } クラス変数 インスタンス count=0 count=1 n adder メソッド スレッドA n=2 adder=4 スレッドB n=3 adder=9 5

Slide 6

Slide 6 text

Javaとマルチスレッド • プログラム開始時はmainメソッドを実行するスレッドが一つのみ存在 する • Threadクラスを使うことで新たにスレッドを作成し、処理を並行して行 える(マルチスレッド) • Threadを使って並行処理を行うためには以下のどちらかの方法で行 う • Threadクラスを継承し、runメソッドをオーバーライドして処理を記述 • Runnableインタフェースを実装したクラスを作成し、runメソッドに処理を記述。 Threadのコンストラクタ引数に作成したクラスを渡す • 後者の例を示す • 前者は継承が使えなくなってしまうのでよくない 6

Slide 7

Slide 7 text

Threadを利用する(1/2) • 実行例 public class Outputter implements Runnable { private String name; public Outputter(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(name + ":" + i); } } } スレッドで実行されるクラス public static void main(String[] args) { Thread thread1 = new Thread(new Outputter("thread1")); Thread thread2 = new Thread(new Outputter("thread2")); thread1.start(); thread2.start(); System.out.println("main thread end"); } 並行処理を開始するメソッド行うクラス 7

Slide 8

Slide 8 text

Threadを利用する(2/2) • Eclipseのデバッガビューを使うと、実行されているスレッドが分かる スレッド開始前 スレッド開始後 8

Slide 9

Slide 9 text

スレッドセーフ 9

Slide 10

Slide 10 text

スレッドセーフ • ローカル変数以外の変数はスレッド間で共有されるため、複数ス レッドから同時に同じ変数にアクセスされると意図せぬ動作をするこ とがある • 複数スレッドから同時にアクセスされても問題ないことをスレッドセー フと呼ぶ • スレッドセーフでない例を示し、それをスレッドセーフにする例を示す 10

Slide 11

Slide 11 text

スレッドセーフでない例1・インクリメント • 複数のスレッドで同一の数値をインクリメントする例 public class Holder { private int count; public void add() { count++; } public int getCount(){ return count; } } スレッドセーフでないクラス public class Counter implements Runnable { private Holder holder; public Counter(Holder holder) { this.holder = holder; } @Override public void run() { for (int i = 0; i < 100000; i++) { holder.add(); } } } スレッドで実行されるクラス public static void main(String[] args) throws InterruptedException { Holder holder = new Holder(); Thread thread1 = new Thread(new Counter(holder)); Thread thread2 = new Thread(new Counter(holder)); thread1.start(); thread2.start(); //スレッドの処理が終わるまで1秒待つ Thread.sleep(1000); // 100000*2で200000となるはずだが… System.out.println(holder.getCount()); } スレッドを実行するメソッド 11

Slide 12

Slide 12 text

スレッドセーフでない例1・インクリメント • さきほどのクラスを実行すると、実行する度に結果のcountが変わる • 「count++」という処理はスレッドセーフではないため • Javaのコードとしては1行だが、実際は以下のような処理に分かれる • 1. 変数countの値を一時領域に保持 • 2. 一時領域の値に1を足す • 3. 変数countに一時領域の値を戻す 12

Slide 13

Slide 13 text

スレッドセーフでない例1・インクリメント • count++はマルチスレッドで以下のような順序で実行されると値が加 算されない • 1. スレッドAでcountの値を一時領域に保持(count=0,一時領域=0) • 2. スレッドBでcountの値を一時領域に保持(count=0,一時領域=0) • 3. スレッドAで一時領域の値に1を足す(count=0,一時領域=1) • 4. スレッドBで一時領域の値に1を足す(count=0,一時領域=1) • 5. スレッドAで変数countに一時領域の値を戻す(count=1,一時領域=1) • 6. スレッドBで変数countに一時領域の値を戻す(count=1,一時領域=1) • この例をスレッドセーフにするために、以下の二つの対応案がある • スレッドA,Bで同時にcount++が呼ばれないように制御する • スレッドセーフなクラスを利用してcountの値を保持する 13

Slide 14

Slide 14 text

synchronizedの利用 • 案1.スレッドA,Bで同時にcount++が呼ばれないように制御する • synchronizedというキーワードを使う • synchronizedが付いた処理をあるスレッドが実行中は、他のスレッドはその インスタンスのsynchronizedな処理を実行することができなくなる(最初のス レッドがメソッドの実行を終わるまで待たされる) • これをロックを取る、という(スレッドAがHolderのロックを取っている) public class Holder { private int count; public synchronized void add() { count++; } } スレッドセーフとなったクラス 14

Slide 15

Slide 15 text

synchronizedの挙動 • ロックがかかるのはインスタンスに対してのため、以下のようにイン スタンスが異なれば複数のスレッドで同時にHolder#addを呼び出せ る public static void main(String[] args) throws InterruptedException { Holder holder1 = new Holder(); Holder holder2 = new Holder(); Thread thread1 = new Thread(new Counter(holder1)); Thread thread2 = new Thread(new Counter(holder2)); thread1.start(); thread2.start(); } スレッドを実行するメソッド 15

Slide 16

Slide 16 text

synchronizedの挙動 • ロックがかかるのはインスタンスに対してのため、以下のように別メ ソッドであってもロックの取得待ちが起きる スレッドから参照されるクラス public class Holder { private int count; public synchronized void add() { count++; } public synchronized void sub() { count--; } } public class Counter1 implements Runnable { @Override public void run() { holder.add(); } } スレッドで実行されるクラス public class Counter2 implements Runnable { @Override public void run() { holder.sub(); } } スレッドA holder.add()を呼び出し スレッドB holder.sub()を呼び出し holderのロックを取得 holderのロックはスレッドAに取られているため待ち addメソッド終了 subメソッド開始 16

Slide 17

Slide 17 text

synchronizedの挙動 • synchronizedは再入性(reentrant)があるため、ロックをかけたスレッ ドと同じスレッドであれば、synchronizedな処理を再度呼び出せる public synchronized void add(int n){ if(n < 100){ // 再入性がないと、ここで処理待ちになってしまう add(n+1); } else{ count += n; } } 例:同一スレッドでsynchronizedなメソッドを複数回呼ぶ例 スレッドA holder.add(5) を呼び出し holderのロック を取得 holder.add(6) を呼び出し 既にholderのロックを持ってい るため、add(6)の処理実行 holder.add(5) の処理実行 17

Slide 18

Slide 18 text

synchronizedの挙動 • synchronizedはメソッド単位ではなく、メソッドの中の処理単位でも書 くことができる • スレッドセーフな他の処理がある場合に効率的にロックを取れる public class Holder { private int count; public void add() { int added = calcCount(); // スレッドセーフな重い処理 // 値の変更箇所のみHolderのロックを取ってスレッドセーフに synchronized(this){ count += added: } } } メソッド単位ではなく処理単位にロックを取る例 18

Slide 19

Slide 19 text

スレッドセーフなクラスの利用 • 案2.スレッドセーフなクラスを利用してcountの値を保持する • AtomicIntegerというスレッドセーフなメソッドを提供するクラスを使う • ロックをかけるよりも効率がよいため、専用のクラスがあれば使うべき public class Holder { private AtomicInteger count = new AtomicInteger(); public void add() { count.incrementAndGet(); } public int getCount(){ return count.get(); } } スレッドセーフとなったクラス 19

Slide 20

Slide 20 text

スレッドセーフでない例2・ArrayList#add • 複数のスレッドで同一のArrayListに要素を追加する例 public class Holder { private List list; public void add(Integer i) { list.add(i); } public List getList(){ return list; } } スレッドセーフでないクラス public class Counter implements Runnable { private Holder holder; public Counter(Holder holder) { this.holder = holder; } @Override public void run() { for (int i = 0; i < 100000; i++) { holder.add(i); } System.out.println(“endAdd”); } } スレッドで実行されるクラス public static void main(String[] args) throws InterruptedException { Holder holder = new Holder(); Thread thread1 = new Thread(new Counter(holder)); Thread thread2 = new Thread(new Counter(holder)); thread1.start(); thread2.start(); //スレッドの処理が終わるまで1秒待つ Thread.sleep(1000); // 100000*2で200000となるはずだが… System.out.println(holder.getList().size()); } スレッドを実行するメソッド 20

Slide 21

Slide 21 text

スレッドセーフでない例2・ArrayList#add • さきほどのクラスを実行すると、実行する度にリストのsizeが変わる • また、まれにArrayIndexOutOfBoundsExceptionが発生する • 該当部のコードは以下(※JDK8の実装) Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 823 at java.util.ArrayList.add(ArrayList.java:463) at jp.gr.java_conf.tsyki.thread.lecture.ThreadListAddSample$CountHolder.add(ThreadListAddSample.java:16) at jp.gr.java_conf.tsyki.thread.lecture.ThreadListAddSample$Counter.run(ThreadListAddSample.java:35) at java.lang.Thread.run(Thread.java:748) public boolean add(E e) { this.ensureCapacityInternal(this.size + 1); //要素を保持している配列が足りなくなったら増やす処理 this.elementData[this.size++] = e; //要素を保持している配列に値を設定 return true; } 21

Slide 22

Slide 22 text

不具合が起きるケース • ArrayListのelementDataの空きが1つだけの状態、 (例:elementData.length=16、size=15) かつ以下の順で実行されるとArrayIndexOutOfBoundsExceptionが発 生する • スレッドAでensureCapacityするがsize+1の領域は空いているため何もしない • スレッドBでensureCapacityするがsize+1の領域は空いているため何もしない • スレッドAでsizeをインクリメントし、sizeの位置に値設定 (length=16,size=16) • スレッドBでsizeをインクリメントし、sizeの位置に値設定時、配列の長さが足り ないので例外発生(length=16,size=17) • この例でもsynchronizedを使った制御と、スレッドセーフなクラスの利 用のどちらでも対応ができる 22

Slide 23

Slide 23 text

スレッドセーフなクラスを使う • コレクションフレームワークにはスレッドセーフな実装が用意されて いる • java.util.concurrentパッケージに存在 • CopyOnWriteArrayList • スレッドセーフな代わりに要素の追加は低速 • ConcurrentHashMap • Collections#synchronizedListでラップする事でも既存のリストをスレッ ドセーフにできる • この場合はイテレートは同期化されないことに注意(次のスライド参照) 23

Slide 24

Slide 24 text

スレッドセーフでない例3・ArrayList#iterator • 複数のスレッドで同一のArrayListに要素を追加、参照する例 @Override public void run() { for (int i = 0; i < 100000; i++) { holder.add(i); for(Integer value : holder.getList()){ System.out.println(value); } } } スレッドで実行されるクラス public class Holder { private List list; public synchronized void add(Integer i) { list.add(i); } public List getList(){ return list; } } addをスレッドセーフにしたクラス Exception in thread "Thread-1" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at jp.gr.java_conf.tsyki.thread.lecture.ThreadListIteratorSample$Counter.run(ThreadListIteratorSample.java:40) at java.lang.Thread.run(Thread.java:748) 24

Slide 25

Slide 25 text

スレッドセーフでない例3・ArrayList#iterator • 前提:拡張for文の実態は以下のようにIteratorというクラスを使った ループである • このIteratorクラスで例外が起きている for(Integer value : holder.getList()){ System.out.println(value); } for (Iterator itr = holder.getList().iterator(); itr.hasNext();) { Integer value = itr.next(); System.out.println(value); } 25

Slide 26

Slide 26 text

スレッドセーフでない例3・ArrayList#iterator • 例外となった箇所のArrayListのコードを見てみると… • modCountはリストが追加、削除されたときに加算される変更回数のこと • ループを開始してから、対象のリストに要素が追加、削除された場合、 ConcurrentModificationExceptionが発生するようになっている • 別スレッドからaddされたのでこのエラーになった • CopyOnWriteArrayListを使うか、ループ前にリストを作り直す private class Itr implements Iterator { int expectedModCount = modCount; public E next() { checkForComodification(); ... } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } 26

Slide 27

Slide 27 text

ループ前にリストを作り直して対応する例 @Override public void run() { for (int i = 0; i < 100000; i++) { holder.add(i); for(Integer value : new ArrayList<>(holder.getList())){ System.out.println(value); } } } スレッドで実行されるクラス 27

Slide 28

Slide 28 text

デッドロック 28

Slide 29

Slide 29 text

デッドロック • synchronizedを利用して1スレッドが複数のインスタンスに対してロッ クを取ろうとすると、スレッド同士が相手が保持しているロックの取得 待ちになり、処理が停止することがある。これをデッドロックと呼ぶ • 例:引数srcListの値をdestListに追加する • 呼ばれ方次第ではロックの取得待ちでデッドロックが発生する public void append(List srcList, List destList) { synchronized (srcList) { for (Integer srcValue : srcList) { synchronized (destList) { destList.add(srcValue); } } } } 29

Slide 30

Slide 30 text

デッドロック • スレッドAでappend(list1,list2)を、 スレッドBでappend(list2,list1)を呼ぶとデッドロック スレッドA list1のロックを 取得 synchronized (srcList) スレッドB synchronized (destList) list2のロックを 取得 list2のロックを取得しようとするが、 スレッドBが取得しているため待機 list1のロックを取得しようとするが、 スレッドAが取得しているため待機 デッドロック 発生 30

Slide 31

Slide 31 text

デッドロックを回避する • ロックを取る順番を同一にするよう書き換える public static void append(List srcList, List destList) { // ロックを取る順序を決めるための値を計算 int srcHash = System.identityHashCode(srcList); int destHash = System.identityHashCode(destList); if(srcHash < toHash){ synchronized (srcList) { for (Integer srcValue : srcList) { synchronized (destList) { destList.add(srcValue); } } } } ... else if(srcHash > toHash){ synchronized (destList) { synchronized (srcList) { for (Integer srcValue : srcList) { destList.add(srcValue); } } } } } else{ // どちらが先か判断できない場合は // あらかじめ用意しておいたロック用インスタンスを使う synchronized (tieLock) { synchronized (srcList) { ... 31

Slide 32

Slide 32 text

デッドロックにならなくなった例 • スレッドAでappend(list1,list2)を、 スレッドBでappend(list2,list1)を呼ぶ(hashCodeはlist1destHashのため、 synchronized (destList) メソッド終了 32

Slide 33

Slide 33 text

デッドロックを検出する • JDK同梱のjstackを使うことでデッドロックが生じている場所を見れる • コマンドプロンプトで「jstack {対象のjavaプロセス番号}」を実行 • 以下のような結果が取得できる "Thread-1" #12 prio=5 os_prio=0 tid=0x000000001f6a6800 nid=0x3ac0 waiting for monitor entry [0x000000002035f000] java.lang.Thread.State: BLOCKED (on object monitor) at jp.gr.java_conf.tsyki.thread.lecture.ThreadDeadLockSample.append(ThreadDeadLockSample.java:64) - waiting to lock <0x000000076b0e0b80> (a java.util.ArrayList) - locked <0x000000076b0e0cf0> (a java.util.ArrayList) at jp.gr.java_conf.tsyki.thread.lecture.ThreadDeadLockSample$SampleThread.run(ThreadDeadLockSample.java:50) "Thread-0" #11 prio=5 os_prio=0 tid=0x000000001f5a0000 nid=0x4e40 waiting for monitor entry [0x000000002025f000] java.lang.Thread.State: BLOCKED (on object monitor) at jp.gr.java_conf.tsyki.thread.lecture.ThreadDeadLockSample.append(ThreadDeadLockSample.java:64) - waiting to lock <0x000000076b0e0cf0> (a java.util.ArrayList) - locked <0x000000076b0e0b80> (a java.util.ArrayList) at jp.gr.java_conf.tsyki.thread.lecture.ThreadDeadLockSample$SampleThread.run(ThreadDeadLockSample.java:50) 33

Slide 34

Slide 34 text

Executor Framework 34

Slide 35

Slide 35 text

Executor Frameworkを利用する • Threadはシンプルなクラスであり使いづらい • 例えば、複数スレッドで計算を行わせ、各スレッドの処理が終わるまで待っ てから結果を集計する、といったことがやりづらい • CountDownLatchなどスレッド間で同期をとるためのクラスは用意はされている • マルチスレッドを扱う場合はExecutor Frameworkを利用するとよい • スレッドの結果を受け取るためのインタフェースが存在する • 使い終わったスレッドを再利用するためのスレッドプールという仕組みがあ る • Threadの代わりにExecutorSeviceを使ってスレッドを実行させる • Runnable#runの代わりにCallable#callを実装して処理を行う 35

Slide 36

Slide 36 text

Executor Frameworkを利用する • Executor Frameworkは以下のように使う • 1. スレッドプールを用意する • Executorsのstaticメソッドを使ってExecutorServiceを作成 • 2. スレッドを開始する • ExecutorService#submitにCallableを実装したクラスを渡してスレッドを開始 • スレッドの処理を待つ場合や結果を取得したい場合、返り値のFutureを保持しておく • 3. 必要ならスレッドの処理が終わるのを待つ • Future#getを呼びだす • 4. スレッドプールに終了を通知する • ExecutorService#shutdownを呼び出す • これをやらないとスレッドプールが保持するスレッドが実行中のままになり、プログラム が終了とならない • スレッドプールによっては一定時間後に自動で終了される 36

Slide 37

Slide 37 text

Executor Frameworkを利用する(返り値なし) public class OutputterExecutor implements Callable { private String name; public OutputterExecutor(String name) { this.name = name; } @Override public Void call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println(name + ":" + i); } return null; } } スレッドで実行されるクラス public static void main(String[] args) { // スレッドプールを作成 ExecutorService executor = Executors.newCachedThreadPool(); Callable call1 = new OutputterExecutor("1"); Callable call2 = new OutputterExecutor("2"); // スレッドを開始 Future future1 = executor.submit(call1); Future future2 = executor.submit(call2); try { future1.get(); //スレッドの処理が終わるまで待つ future2.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.println("main thread end"); executor.shutdown(); //スレッドプールに終了を通知 } 並行処理を開始するメソッド行うクラス 37

Slide 38

Slide 38 text

Executor Frameworkを利用する(返り値あり) public class OutputterExecutor implements Callable { private String name; public OutputterExecutor(String name) { this.name = name; } @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } } スレッドで実行されるクラス public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); Callable call1 = new OutputterExecutor("1"); Callable call2 = new OutputterExecutor("2"); Future future1 = executor.submit(call1); Future future2 = executor.submit(call2); int sum = 0; try { //スレッドの処理が終わるまで待って計算結果を受け取る sum += future1.get(); sum += future2.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.println("main thread end"); executor.shutdown(); } 並行処理を開始するメソッド行うクラス 38

Slide 39

Slide 39 text

スレッドプールについて • Executors.newCachedThreadPool • 必要に応じてスレッドを作成する • スレッドの処理完了後、60秒後にそのスレッドは破棄される。60秒以内であ ればスレッドは再利用されるため効率がよい • スレッドが多くなりすぎるとCPUがスレッドを切り替える処理(コンテキストス イッチ) のコストが大きくなり、パフォーマンスが落ちるため注意 • Executors.newFixedThreadPool(nThreads) • 引数で指定されたスレッド数を上限として実行する • スレッドの数が多くなりすぎないようにできる • DBへの同時アクセス数が制限されている、というような場合にも利用できる • 実行中のスレッドが全て他のスレッドの結果を待っているような状態の場合、 デッドロックが発生することに注意 39

Slide 40

Slide 40 text

スレッドの結果取得について • Callable#callで例外が発生した場合、Callable#getを呼び出した時点 でExecutionExceptionが発生する • 発生した例外はExecutionExceion#getCauseで取得できる • Callable#getで処理の終了を待つ際、時間制限を付けたい場合は Future#getの引数あり版を使う • future.get(500, TimeUnit.MILLISECONDS); • 指定時間内に処理が完了しなかった場合、TimeoutExceptionが発生する 40

Slide 41

Slide 41 text

Fork/Join Framework 41

Slide 42

Slide 42 text

Fork/Join Frameworkを利用する • JDK1.7から追加された、特定状況下で効率よくスレッドを使うための 仕組み • 処理を(再帰的に)分割して実行するのに適している。 • 「処理(データ)を分割してそれぞれを子スレッドで並列実行し、それが終わ ると自分自身のスレッドも終了」という処理が向いている。 • Fork/Joinフレームワークでは他のスレッドの処理を待っているスレッドを使い まわすという特徴がある • このような処理を通常のスレッドプールを使ってやると、スレッド数が膨大に なってしまい効率が悪い • 例:指定のフォルダ以下のファイルをカウントする 42

Slide 43

Slide 43 text

再帰的にファイル数を取得する例 root dir1 dir2 dir3 dir1-1 dir1-2 ①スレッド1でroot以下のフォルダを探索。 フォルダ毎にそのフォルダ内を探索するスレッドを作成する (dir1,2,3で3スレッド追加)。各スレッドの結果が出るまで待ち ②スレッド2でdir1以下の フォルダを探索(dir1- 1,dir1-2で2スレッド追加)。 各スレッドの結果が出る まで待ち • 通常だと最大でフォルダ数分のスレッドが必要になってしまう • スレッドプールが固定数だと、スレッドが足りなくなってデッドロックする • Fork/Joinを使うと、処理待ちのスレッドを使いまわすため、スレッド数 が減る。上記の例だと、dir1-1の探索時にスレッド1が使われうる 43

Slide 44

Slide 44 text

Fork/Join Frameworkを利用する • Fork/Joinは以下のように使う • 1. スレッドプールを用意する • ForkJoinPoolをnewする • 2. スレッドを開始する • ForkJoinPool#submitにForkJoinTaskを継承したクラスを渡してスレッドを開始 • 返り値でForkJoinTaskが返るが、これは引数で指定したものと同じなので不要 • 3. 必要ならスレッドの処理が終わるのを待つ • ForkJoinTask#joinを呼びだす • 実装例は以下を参照 • https://github.com/tsyki/concurrent- example/blob/master/src/jp/gr/java_conf/tsyki/thread/lecture/ForkJoinFileCounter.java • 今回のスライドに記載のサンプルコードも上記githubにあり 44

Slide 45

Slide 45 text

まとめ • Threadクラスを利用することで並行処理ができる • 並行処理で同じデータを書き換える場合は不整合がおきないように する必要がある(スレッドセーフ) • synchronizedを使うことでロックを取得する • スレッドセーフなクラスを使う • synchronizedを使う場合はデッドロックが発生しないようにする • 1スレッドで複数のロックを取る場合、常に同じ順番でロックを取る • Threadよりも実践的なクラスとしてExecutor Frameworkがある • 並行処理の結果を待って、計算結果を取得するインタフェースがある • 再帰的にスレッドを作成する場合はFork/Joinを使うと効果的 45