Java Gold合格への道 ~並行処理・CyclicBarrier~
こんにちは。たろすです。
今回は並行処理におけるCyclicBarrierについて説明します。
CyclicBarrierとは
CyclicBarrierは並行処理において各スレッドの足並みをそろえるときに使うクラスです。
例えば以下のようなコードを実行すると、
public class Main { public static void main(String[] args) { Thread t0 = new Thread(new Runnable() { public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":start"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName + ":end"); } }); Thread t1 = new Thread(new Runnable() { public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":start"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName + ":end"); } }); Thread t2 = new Thread(new Runnable() { public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":start"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadName + ":end"); } }); t0.start(); t1.start(); t2.start(); } }
Thread-0:start Thread-1:start Thread-2:start Thread-0:end Thread-1:end Thread-2:end
「Thread-0:end」と表示された後約4秒後に「Thread-1:end」と表示され、さらに約5秒後に「Thread-2:end」と表示されます。
一方で、CyclicBarrierを使うと、
public class Main { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(2); Thread t0 = new Thread(new Runnable() { public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":start"); try { Thread.sleep(1000); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println(threadName + ":end"); } }); Thread t1 = new Thread(new Runnable() { public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":start"); try { Thread.sleep(5000); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println(threadName + ":end"); } }); Thread t2 = new Thread(new Runnable() { public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":start"); try { Thread.sleep(10000); barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println(threadName + ":end"); } }); t0.start(); t1.start(); t2.start(); } }
Thread-1:start Thread-0:start Thread-2:start Thread-1:end Thread-0:end
実行から約10秒経ってから「Thread-0:end」と「Thread-1:end」が同時に表示されます。
ちなみに「Thread-2:end」が表示されないのはコンストラクタの引数(足並みをそろえるスレッドの数)を2に設定しているからです。
先に到着した二つのスレッドは先に行ってしまい、残りのスレッドは一つになってしまったので最後のスレッドは永遠に待機状態のままです。
Java Gold合格への道 ~並行処理・並行コレクション~
こんにちは。たろすです。
今回は並行処理における並行コレクションについて説明します。
コレクションと並行コレクションの違い
コレクションと言えばArrayListやHashMapなどがあります。
これらはスレッド・セーフではなく、並行処理においてConcurrentModificationExceptionが発生する可能性があります。
一方で並行コレクションはスレッド・セーフのためこの例外は発生しません。
並行コレクションにはCopyOnWriteArrayListやConcurrentMapなどがあります。
試しにCopyOnWriteArrayListを使ってその違いを確認してみましょう。
ArrayListでの実装
public class Main { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { list.add(i); } Thread t1 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 1000; i++) { list.remove(0); } } }); Thread t2 = new Thread(new Runnable() { public void run() { for (Integer num : list) { System.out.println(num); } } }); t1.start(); t2.start(); } }
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 collection.Main$2.run(Main.java:27) at java.lang.Thread.run(Thread.java:748)
上記のコードは0~999までの数値をArrayListに格納し、先頭から削除すると同時に走査しています。
当然走査している最中にArrayListが変更されてしまうので、ConcurrentModificationExceptionが発生します。
CopyOnWriteArrayListでの実装
これをConcurrentModificationExceptionが発生しないようにCopyOnWriteArrayListで実装するとどうなるかというと、
public class Main { public static void main(String[] args) { List<Integer> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 1000; i++) { list.add(i); } Thread t1 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 1000; i++) { list.remove(0); } } }); Thread t2 = new Thread(new Runnable() { public void run() { for (Integer num : list) { System.out.println(num); } } }); t1.start(); t2.start(); } }
76 77 省略 998 999
Listの定義をArrayListからCopyOnWriteArrayListに変えただけです。
CopyOnWriteArrayListを使うことによりConcurrentModificationExceptionが発生しなくなりました。
Java Gold合格への道 ~並行処理・アトミック変数~
こんにちは。たろすです。
今回は並行処理におけるアトミック変数について説明します。
アトミック変数とは
アトミックとは「原子の」という意味を持ちます。
並行処理におけるアトミックとは複数の処理を一つの操作として扱い、途中から介入できない性質を言います。
アトミック変数はスレッド・セーフなプログラミングを可能にします。
例
上記の説明ではわかりにくいと思います。
例を出してみましょう。
public class ThreadSample extends Thread { static private int num; public void run() { for (int i = 0; i < 100000; i++) { System.out.println(++num); } } }
public class Main { public static void main(String[] args) { ThreadSample thread1 = new ThreadSample(); ThreadSample thread2 = new ThreadSample(); ThreadSample thread3 = new ThreadSample(); thread1.start(); thread2.start(); thread3.start(); } }
上記のMainを実行したときの最終的なnumの値はいくつになるでしょうか。
3つのスレッドで100000回ずつインクリメントしているから300000?
そうとは限りません。
threa1とthread2が同時にnumにアクセスしてそれぞれインクリメントし、numに上書きした場合、本来2回インクリメントされているから+2されるべきところが同じ値に対してインクリメントしたため+1しかされません。
一方で、アトミック変数を使った場合どうなるか。
public class ThreadSample extends Thread { static private AtomicInteger num = new AtomicInteger(0); public void run() { for (int i = 0; i < 100000; i++) { System.out.println(num.incrementAndGet()); } } }
同じmainを実行すると必ず結果は300000になります。
これはスレッド・セーフであるから、つまりあるスレッドが変数にアクセスしているときに他のスレッドは変数にアクセスできないようになっているからです。
Java Gold合格への道 ~Java I/O・ファイルツリーのトラバース~
こんにちは。たろすです。
今回はJava I/Oのファイルツリーのトラバースについて説明します。
方法
ファイルのトラバースはwalkFileTreeメソッドやwalkメソッドで行います。
walkFileTree
try { Files.walkFileTree(Paths.get("dir0"), new SimpleFileVisitor<Path>() { public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException { System.out.println(file); return FileVisitResult.CONTINUE; } }); } catch (IOException e) { e.printStackTrace(); }
dir0 dir0\dir1 dir0\dir2 dir0\dir2\dir3 dir0\dir2\dir4 dir0\dir2\dir4\dir5
第一引数は始点となるパスで、第二引数はFileVisitorインタフェースです。
SimpleFileVisitorクラスはFileVisitorクラスの簡易実装として四つのメソッドの実装を提供しています。
今回はディレクトリ内のエントリをたどる前に呼び出されるpreVisitDirectoryのみオーバーライドしました。
その他に以下のメソッドがあります。
postVisitDirectory:ディレクトリ内のエントリおよびそれらのサブディレクトリをたどった後に呼び出される。
visitFile:ディレクトリ内のファイルをたどった際に呼び出される。
visitFileFailed:たどれなかったファイルに対して呼び出される。
walk
try { Files.walk(Paths.get("dir0")).forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); }
dir0 dir0\dir1 dir0\dir2 dir0\dir2\dir3 dir0\dir2\dir4 dir0\dir2\dir4\dir5
walkメソッドも第一引数は始点となるパスです。
戻り値はStream<Path>オブジェクトです。
Java Gold合格への道 ~Java I/O・Filesクラス~
こんにちは。たろすです。
今回はJava I/OのFilesクラスについて説明します。
FileクラスとFilesクラスの違い
FileクラスではFileオブジェクトを生成し、メソッドを呼ぶことでファイル操作をしていました。
一方でFilesクラスはstaticメソッドのみで、引数にPathオブジェクトをとることでファイル操作します。
Filesクラスの主なメソッド
try { // ファイルのコピー System.out.println("filesディレクトリの中身(コピー前):"); Files.list(Paths.get("files")).forEach(System.out::println); System.out.println(); Files.copy(Paths.get("files/text.txt"), Paths.get("files/text2.txt")); System.out.println("filesディレクトリの中身(コピー後):"); Files.list(Paths.get("files")).forEach(System.out::println); System.out.println(); // ファイルの移動 System.out.println("tmpディレクトリの中身(移動前):"); Files.list(Paths.get("files/tmp")).forEach(System.out::println); System.out.println(); Files.move(Paths.get("files/text2.txt"), Paths.get("files/tmp/text2.txt")); System.out.println("filesディレクトリの中身(移動後):"); Files.list(Paths.get("files")).forEach(System.out::println); System.out.println(); System.out.println("tmpディレクトリの中身(移動後):"); Files.list(Paths.get("files/tmp")).forEach(System.out::println); System.out.println(); // 属性の確認 System.out.println("最終更新日時:" + Files.getAttribute(Paths.get("files/text.txt"), "lastModifiedTime")); System.out.println("最終アクセス日時:" + Files.getAttribute(Paths.get("files/text.txt"), "lastAccessTime")); System.out.println("作成日時:" + Files.getAttribute(Paths.get("files/text.txt"), "creationTime")); System.out.println("サイズ:" + Files.getAttribute(Paths.get("files/text.txt"), "size")); } catch (IOException e) { e.printStackTrace(); }
filesディレクトリの中身(コピー前): files\text.txt files\tmp filesディレクトリの中身(コピー後): files\text.txt files\text2.txt files\tmp tmpディレクトリの中身(移動前): filesディレクトリの中身(移動後): files\text.txt files\tmp tmpディレクトリの中身(移動後): files\tmp\text2.txt 最終更新日時:2022-02-01T09:12:38.929466Z 最終アクセス日時:2022-02-19T09:47:20.39287Z 作成日時:2022-02-01T08:01:59.585525Z サイズ:11
Java Gold合格への道 ~Java I/O・Pathインタフェース~
こんにちは。たろすです。
今回はJava I/OのPathインタフェースについて説明します。
Pathインタフェースとは
PathインタフェースはJava SE 7から追加されたファイルやディレクトリのパスを表すクラスです。
Java SE 6以前はFileクラスが用いられて気ましたが、様々な扱いにくさがありPathインタフェースが追加されました。
Pathインタフェースの主なメソッド
Path path = Paths.get("C:/aaa/bbb/ccc"); System.out.println("ルート:" + path.getRoot()); System.out.println("最下層のパス名:" + path.getFileName()); System.out.println("ルートを除くパス階層数:" + path.getNameCount()); System.out.println("パスの抜き出し:" + path.subpath(1, 2)); System.out.println("解決(絶対パス):" + path.resolve(Paths.get("D:/ddd"))); System.out.println("解決(相対パス):" + path.resolve(Paths.get("eee"))); System.out.println("親パスに対して解決(絶対パス):" + path.resolveSibling(Paths.get("D:/ddd"))); System.out.println("親パスに対して解決(相対パス):" + path.resolveSibling(Paths.get("eee"))); Path p0 = Paths.get("C:/aaa/./bbb/../ccc"); System.out.println("冗長なパスを正規化:" + p0.normalize()); Path p1 = Paths.get("C:/ddd/eee"); System.out.println("相対パスとして解決:" + path.relativize(p1));
ルート:C:\ 最下層のパス名:ccc ルートを除くパス階層数:3 パスの抜き出し:bbb 解決(絶対パス):D:\ddd 解決(相対パス):C:\aaa\bbb\ccc\eee 親パスに対して解決(絶対パス):D:\ddd 親パスに対して解決(相対パス):C:\aaa\bbb\eee 冗長なパスを正規化:C:\aaa\ccc 相対パスとして解決:..\..\..\ddd\eee
getRoot
パスのルートを返します。
getNameCount
ルートを除いたパス階層の数を返します。
パスがルートだけの場合は0を返します。
subpath
パスの一部を抜き出して返します。
resolve
指定されたパスを呼び出し元のパスに対して解決します。
具体的には、引数が絶対パスの場合は引数のパスがそのまま返されます。
相対パスの場合には呼び出し元のパスに引数のパスを結合したものを返します。
resolveSibling
引数のパスを呼び出し元の親パスに対して解決します。
具体的には、引数が絶対パスの場合は引数のパスがそのまま返されます。
相対パスの場合には呼び出し元の親パスに引数のパスを結合したものを返します。
relativize
呼び出し元のパスから引数のパスへの相対パスを返します。
Java Gold合格への道 ~Java I/O・オブジェクトの直列化と復元~
こんにちは。たろすです。
今回はJava I/Oのオブジェクトの直列化と復元について説明します。
直列化はなんのために行う?
そもそも直列化とはなにかというと、オブジェクトをバイト列として一列に並べることです。
そうすることによって、アプリケーションが終了してもオブジェクトを保存しておいて、再度アプリケーションが起動したときにオブジェクトを復元できます。
また、直列化されたオブジェクトはネットワーク上で送信することもできます。
直列化
直列化はwriteObjectメソッドによって行います。
直列化したいオブジェクトにはSerializableインタフェースを実装します。
public class Data implements Serializable { private int id; private int num; public Data(int id, int num) { this.id = id; this.num = num; } public String toString() { return id + ":" + num; } // setter & getter }
Data outData = new Data(1, 5); try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sample.ser"))) { oos.writeObject(outData); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
復元
復元はreadObjectメソッドを使って行います。
戻り値はオブジェクト型なので元の型にキャストします。
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("sample.ser"))) { Data inData = (Data) ois.readObject(); System.out.println(inData); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); }
1:5