Java SE8 Gold 研修 3 日目 マルチスレッドの復習として有意義な時間だった…. Fork/Join Frameworkはこういう書き方だ!だけでいいやってなりました(真顔
同時実行性(マルチスレッド)
タスクスケジューリング
プロセス
コードとデータの両方を含むメモリ領域 CPU のタイム・スライスを受け取るようにスレッドがある
スレッド
プログラムを実行する「主体」 それ自体が 1 つのシステム・リソース デフォルトではモノスレッド
Java SE ではプログラミングで複数のスレッドを生成・制御可能 → マルチスレッド・プログラミング
従来の Thread と Runnable
Java 5 以前
Thread クラスの拡張
class Test{ public static void main(String args[]){ ThreadTest t = new ThreadTest(); t.start(); // ここで別スレッドが生成される System.out.println("Hello World"); } } class ThreadTest extends Thread{ public void run(){ System.out.println("Hello Thread"); } }
Runnable インタフェースの実装
public class ExampleRunnable implements Runnable { private final String name; public ExampleRunnable(String name){ this.name = name; } @Override public void run(){ for(int i = 0; i < 10; i++){ System.out.println(name + ":" + i); } } }
java.util.concurrent パッケージ
従来の Thread 関連の API では適切にコード化するのが困難だった そこで Java 5 から新機能が導入
- 同時コレクション
- 同期化及びロックの代替方法
- スレッド・プール
Executor Service
タスクの実行に使用される高度なメカニズム 現在の Java のマルチスレッド・プログラミングではこの機能を使うことを推奨
- Thread オブジェクトを作成したり,再利用可能
- Task を送信して,その結果を後で確認可能
- Task は
java.lang.Runnable
とjava.util.concurrent.Callable
がある
TODO : 後日RunnableとCallable,Executor Serviceの関係を図式化
- 実装インスタンスは Executors を使用して取得
- スレッド・プールのスレッド数を宣言しない場合
ExecutorService es = Executors.newCachedThreadPool(); es.execute(new ExampleRunnable("first")); es.execute(new ExampleRunnable("second")); es.shutdown(); // 停止
- スレッド・プールのスレッド数を明示的に設定する場合(PC の CPU のコア数を参考に設定)
int cpuCount = Runtime.getRuntime().availableProcessors();
ExecutorService es = Executors.newFixedThreadPool(cpuCount);
スレッドの問題
スレッドセーフ
複数のスレッドからアクセスされたときでも正しく動作し続ける,クラスの機能
デッドロック
スレッド A はスレッド B から条件が設定されるのを待っている間にブロックされ, スレッド B もまたスレッド A から条件が設定されるのを待っている間ブロックされている状態 JVM には自動的に解消する機能がない 解消するためにはプロセス停止するしかない → そうならないようにプログラムを組み立てる必要がある
ライブロック
スレッドはブロックされていないが,スレッド操作が失敗の再試行を続けるため, 処理を進めることができない状態
共有データの問題
- static 及びインスタンス・フィールドは,スレッドによって共有される可能性がある
非共有データ
一部の変数型は共通されない.次の型は常にスレッドセーフ
- ローカル変数
- メソッドのパラメタ
- 例外ハンドラ・パラメタ
- 不変データ(String オブジェクトまたは final フィールドなど)
原子的(Atomic)操作
単一のデータ操作のこと Java における 1 文が常に Atomic 操作とは限らない 例
int a = 1; // ①変数aの値をtemp領域にコピー a++; // ②temp領域の値に+1して変数aに書き戻す
期待動作 スレッド A が a++;を実行して a が 2 に. スレッド B が a++;を実行して a が 3 に.
懸念 スレッド A が ② で a に書き戻す前に,スレッド B が ① を実行する場合があり, スレッド A の実行結果は a=2,スレッド B の実行結果も a=2 になることがある.
自分の思い通りの処理ができていない動作(アウトオブオーダー実行)が生じる場合がある → 排他制御/同期化が必要!
synchronized
スレッドセーフでないデータに複数のスレッドが同時にアクセスしないように使用 他のスレッドが待たされるため,性能劣化になりやすい(注意)
synchronized メソッド
public class SynchronizedCounter { private static int i = 0; // synchronized メソッド public synchronized void increment(){ i++; } }
synchronized ブロック
主流 synchronized メソッドに比べて,シングル・スレッドに限定している行数を減らせる
public void run(){ for(int i = 0; i < countSize; i++){ // synchronized ブロック synchronized(this){ count.increment(); } } }
java.util.concurrent.atomic パッケージ
単一の変数に対するロックフリーでスレッドセーフなプログラミングを サポートするクラスを含む
例 AtomicInteger
AtomicInteger ai = new AtomicInteger(1); ai.incrementAndGet(); // ++i; ai.getAndIncrement(); // i++;
CyclicBarrier
スレッドの同期化ができる(シンクロナイザ) スレッドの完了を指定された数のスレッドが待機するまでブロックするクラス
await()
で指定した数のスレッドが揃ったタイミングで特定アクションを実行可能
// 3つのスレッドが揃うまで待つ CyclicBarrier barrier = new CyclicBarrier(3);
// 3つのスレッドが揃うまで待ち,揃ったらバリアアクションを実行 CyclicBarrier barrier = new CyclicBarrier(3, ≪Runnable≫ barrierAction);
コンカレント・コレクション
java.util.Collections はスレッドセーフではない. スレッドセーフでコレクションを使用する方法は以下
- synchronized ブロックを使う
- ライブラリメソッドを利用して同期化したラッパーを作成
ex) java.util.Collections.synchronizedList(List
) - java.util.concurrent コレクションを使用 ex) ConcurrentHashMap, ConcurrentSkipListSet
「コレクションがスレッドセーフ ⇒ その要素がスレッドセーフ」になるわけではない
Fork/Join フレームワーク
ちなみに Fork:分岐・分割,Join:統括・統合という意味
並列処理
最新のシステムには複数の CPU が含まれている. 何らかの方法でスレッドを活用しないと, システムの処理能力の一部のみが使用される. → 並列処理で効率化
単純な並列ソリューションでは処理データが複数セットに分割される CPU ごとに 1 つのデータ・セットと,各データ・セットを処理する 1 つのスレッドがある.
Fork/Join フレームワークの必要性
並列処理の注意点
- CPU が異なる速度で動作する可能性がある
- 非 Java タスクが CPU 時間を必要とするために,Java スレッドが CPU での実行に使用できる時間が減る可能性
- 分析されているデータによって異なる処理時間が必要になる場合がある
1つのタスクを複数に分割して,複数のスレッドで並列処理し 結果を統合して得る機能が Fork/Join フレームワーク
java.util.concurrent.ForkJoinTask\<V>
難しい.ソースコード例は以下のサイトで理解するのがよい. 正直これはコードの書き方の雰囲気を把握すればいいらしい. www.ne.jp
手軽な並列処理
Java SE8 の Stream API を利用して,ストリーム生成後に
メソッドチェーンにparallel()
を加えるだけで並列処理になる
JDBC を使用する DB アプリケーション構築
JDBC
Java Database Connectivity Java から RDBMS に接続するための標準インタフェース 接続先の DB 種類に拘わらず,共通 API を用いて DB を操作するアプリを開発可能
Java の API パッケージは java.sql 及び javax.sql パッケージに含まれている. JDBC API を使用するためには接続先の DB をサポートしている JDBC Driver が必要
JDBC 処理の基本的な流れ(Java SE アプリの場合)
①DB 接続(Connection
オブジェクト取得)
Connection con = DriverManager.getConnection(url, username, password);
↓
②SQL 発行準備(Statement
オブジェクト取得)
Statement stmt = con.createStatement();
↓
③SQL 発行(ResultSet
オブジェクト取得)
// SELECT文の場合 String selectQuery = "SELECT * FROM CUSTOMER"; ResultSet rs = stmt.executeQuery(query); // 戻り値はResultSet // INSERT, UPDATE, DELETE文の場合 String updateQuery = "UPDATE FROM CUSTOMER WHERE ID = 'AA11'"; int count = executeUpdate(updateQuery); // 戻り値はint(更新された行数) // 全てのSQLコマンド boolean isResultSetExists = execute(query); // 戻り値はboolean
↓ ④SELECT 文の場合は結果を取得
while(rs.next()){ int cusID = rs.getInt("ID"); String first = rs.getString("FirstName"); System.out.println("Customer ID: " + cusID + "," + "FirstName : " + first); }
↓
⑤ 使用オブジェクトの解放
各オブジェクトのclose()
実行するかtry-with-resource
を利用する
try-with-resource 利用例
try(Connection con = DriverManager.getConnection(url,username,password);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(query)
){
SQLException
生成された DB エラーに関する詳細の報告に使用
PreparedStatement の使用
Statement のサブクラス SQL 文を複数回実行する場合に役立つ 解析処理数が減るため,性能向上が期待できる
//Statementの場合 Statement stmt = conn.createStatement(); for(int id = 0; id < 100; id ++){ String sql = "SELECT ENAME FROM EMP WHERE EMPNO = " + id; ResultSet rs = stmt.executeQuery(sql); while(rs.next()){ // 表示処理 } } //PreparedStatementの場合 String sql = "SELECT ENAME FROM EMP WHERE EMPNO = ?"; PreparedStatement ps = conn.prepareStatement(sql); for(int id = 0; id < 100; id ++){ ps.setInt(1, id); // 1番目のはてなに入れる try(ResultSet rs = ps.executeQuery();){ while(rs.next()){ // 表示処理 } } }
PreparedStatement は try-with-resources の入れ子になることが多い
CallableStatement
ストアド・プロシージャやストアド・ファンクションを実行する場合に利用
ローカライゼーション
ローカル固有のコンポーネントを追加してテキストを変換し, ソフトウェアを特定の地域または言語に適用させるプロセス
言語に加え,日付・数値・通貨などの文化に依存する要素も変換が必要
Locale
Java では(言語コード 2 桁)_(国コード 2 桁)_(付加情報)でロケール情報を管理 ex) 日本は ja_jp, アメリカは en_US
Locale オブジェクト取得方法
// Localeコンストラクタで取得 Locale loc = new Locale("ja","JP"); // Localeの定数で取得 Locale loc = Locale.JAPAN; // LocaleのBuilderメソッドで取得 Locale loc = new Locale.Builder().setLanguage("ja").setRegion("JP").build(); // Localeのデフォルトを取得 Locale loc = Locale.getDefault();
デフォルトのロケールは OS 設定依存
或いはjava -Duser.language=ja -Duser.country=JP
のように指定可能
ResourceBundle
ロケール固有のデータを分離する
- 個別に格納されている KeyValue のペアを返す
java.util.ResourceBundle
または.properties
ファイルの場合がある- 優先順位としては
ResourceBundle
クラス >.properties
ファイルで読み込まれる
ex) MessageBundle_xx_YY.properties
通貨の書式設定
java.text.NumberFormat
から通貨インスタンスを取得- Double を
format()
に渡す
Locale loc = Locale.UK; NumberFormat nf = NumberFormat.getCurrencyInstance(loc); double money = 1_000_000.00d; System.out.println("Money: " + nf.format(money) + " in Locale: " + loc);
日付の書式設定
- Locale に基づいて
DataFormat
オブジェクトを取得 - LocalDateTime変数からフォーマッタを渡す
format()
を呼び出す
LocalDateTime today = LocalDateTime.now(); Locale loc = Locale.FRANCE; DateTimeFormatter df = DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL).withLocale(loc); System.out.println("Date:" + today.format(df) + " Locale: " + loc.toString());
終わりに
講義 → 演習形式のオーソドックスな研修. 受講テキストの説明が豊富.紫本の代わりになるのかな.
こちらの qiita 記事が参考になりそう. qiita.com
3 月中に合格目指して頑張ります. ただ Gold 受験するためにはまずは Silver 合格しないといけない. 2月上旬までに受けます.黒本1周してみたけどそんなに難しくなかった.
Java SE 11 Gold本…紫本でも黒本でも白本でも何でもいいから出版されないかな…