Java SE8 Gold (3)

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.Runnablejava.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 を操作するアプリを開発可能

JavaAPI パッケージは 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 Silver問題集[1Z0-815]対応

徹底攻略Java SE 11 Silver問題集[1Z0-815]対応

Java SE 11 Gold本…紫本でも黒本でも白本でも何でもいいから出版されないかな…