IBMメインフレームのマルチスレッドJava環境(WAS for z/OS環境)でデータセットの排他制御を実現する

# 概要
JZOS[[参考リンク 1](https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.80.doc/zsecurity-component/jzos.html)]を使うと、z/OS環境（IBMメインフレーム環境）で、データセットの入出力を行うJavaプログラムを開発することができます[[参考リンク 6 ](https://qiita.com/tsunogai/items/8f75ce6a2f52c6c61ff5)]。シングル・スレッド環境で動くJavaアプリケーションであれば、データセットの排他制御はOSに任せておけば問題ありません。データセットのアロケーション時に指定するDISPパラメーター(SHR/OLD)によって下表のように排他制御されますので、アプリケーションとして特別な制御ロジックを実装する必要はありません。この場合の排他制御はプロセス間の排他制御であり、COBOL言語やPL/I言語で書かれたバッチ・プログラムをJCLで実行する際の排他制御と同じです（下記**＜COBOLバッチにおけるデータセットの排他制御＞**参照）。

|新規要求\実行中処理|読み取り中(SHR)|書き込み中(OLD)|
|:--------------------------:|:------:|:------:|
|読み取り(SHR)|○|×　(注1)|
|書き込み(OLD)|×　(注1)|×　(注1)|
**注1** JCLの場合は、アクセス要求が競合すると新規要求は待ち状態になり、実行中の処理が終了してデータセットが開放されるとアクセス可能になるが、BPXWDYNによるダイナミックアロケーションの場合は即時エラーになる。なお、書き込み時にDISP=OLD、読み取り時にDISP=SHRを指定するのは、JCLで排他制御をする際の慣習であり、DISP=SHRで書き込み処理したとしても、それ自体が原因でエラーが起きるわけではない。

**＜COBOLバッチにおけるデータセットの排他制御＞**
下図のようにジョブ#1のCOBOLバッチ#1がDISP=OLDで書き込み中のデータセットに対して、ジョブ#2のCOBOLバッチ#2がDISP=OLDでアクセスを試みると、OSの排他制御により、COBOLバッチ#2はデータセットが開放されるまで待たされます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124479/b7c4999b-2951-4dfe-7dac-597b73719cd7.png)

ところが、マルチスレッド環境のJavaプログラムでは、排他制御を全てOSまかせにすることはできません。以下にマルチスレッドJava環境でデータセットを扱う際のアクセス競合関連の課題とJZOS APIによる解決方法をサンプルを交えて説明します。

# マルチスレッドJava環境での課題
シングル・スレッド環境のJavaプログラムからデータセットにアクセスする場合は、競合に関して特別な対応は不要ですが、WebSphere Application Server for z/OS（以降WAS for z/OSと表記）で動くサーバー・サイドJavaアプリケーションからデータセットにアクセスする場合は考慮が必要です。WAS for z/OS環境は、マルチスレッド環境（Java EE環境）だからです。前述のOSによる排他制御は、複数プロセス間で同一データセットにアクセスする場合の話であって、同一プロセス内の複数スレッドから同一データセットにアクセスする場合は、アプリケーションの責任で排他制御ロジックを実装してデータの破壊を防ぐ必要があります（下記 **＜WAS for z/OS上のJavaバッチにおけるデータセット排他制御の課題＞** 参照）。

**＜WAS for z/OS上のJavaバッチにおけるデータセット排他制御の課題＞**
下図のJavaバッチ#1とJavaバッチ#2の排他制御は有効に機能しますが、Javaバッチ#1とJavaバッチ#3のような同一アドレス空間（Unix/Windowのプロセスに相当）内の複数スレッド間の排他制御はアプリケーションの責任で実装する必要があります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124479/03dff4d6-8ef4-d7cc-742e-241d75fc22c6.png)

# com.ibm.jzos.Enqueueクラスで排他制御ロジックを実装
では具体的にどうすれば排他制御を実装できるかというと、これまたJZOSのAPI(com.ibm.jzos.Enqueueクラスが提供するメソッド[[参考リンク 2](https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.api.80.doc/com.ibm.jzos/com/ibm/jzos/Enqueue.html)]で実現できてしまいます。

以下にサンプル・プログラム`EnqTest.java`を示します。
実装の要点を伝えるため、下記サンプルはシンプルなスタンドアロンJavaアプリケーションの形態を採っていますが、mainメソッドに記述されたロジックは、Java EEアプリケーションでもほぼそのまま使える内容です(**注2**)。

```EnqTest.java
package sample;
import com.ibm.jzos.ZFile;
import com.ibm.jzos.Enqueue;
import javax.xml.bind.DatatypeConverter;

public class EnqTest {
	public static void main(String args[]) throws Exception {
		String qname = "TEST_Q01";
		String dsName = "USER001.TEST.DATA1";
		Enqueue enq = new Enqueue(qname, dsName);
		enq.setScope(Enqueue.ISGENQ_SCOPE_SYSTEM);
		enq.setControl(Enqueue.ISGENQ_CONTROL_SHARED);      // <- 読み込み処理の場合
		//enq.setControl(Enqueue.ISGENQ_CONTROL_EXCLUSIVE); // <- 書き込み処理の場合
		enq.setContentionActWait();
		
		try{
			enq.obtain();
			byte[] token = enq.getEnqToken();
			System.out.println("token:"+DatatypeConverter.printHexBinary(token));
			
			//ここにデータセット入出力処理を記述
			
		} catch(Throwable t) {
			t.printStackTrace();
		} finally {
			if (enq.getEnqToken() != null) {
				enq.release();
			}

		}
	}

}

```
**ここがポイント!**
以下にサンプル・プログラムのポイントを説明します。上記サンプルは排他制御にフォーカスしているため、データセットの入出力処理については割愛しています。データセットの入出力処理の具体的な内容について知りたい方は、[「参考リンク 6. JZOSを使ってJavaからデータセットにアクセスしてみる」](https://qiita.com/tsunogai/items/8f75ce6a2f52c6c61ff5)で確認していただければと思います。

- 排他制御は、JZOS提供のcom.ibm.jzos.Enqueueクラスを利用して実装します**(注3)**。
- コンストラクターにキュー名とリソース名を指定してEnqueueオブジェクト生成します。
 - キュー名には8文字以内の名前を指定します。
 - リソース名は1バイト～255バイトの名前を指定します。
- Enqueue#setControlメソッドで下記いずれかのスコープを設定します。指定したスコープと上記**キュー名+リソース名**で排他制御対象リソースを識別し、エンキュー／デキューします**（注2）**。
 - **Enqueue.ISGENQ_SCOPE_STEP**　アドレス・スペース内
 - **Enqueue.ISGENQ_SCOPE_SYSTEM**　単一システム内（デフォルト）
 - **Enqueue.ISGENQ_SCOPE_SYSTEMS**　シスプレックス全体
 - **Enqueue.ISGENQ_SCOPE_SYSSPLEX**　シスプレックス全体（SYSTEMSと同じ）
- Enqueue#setControlメソッドで下記いずれかの排他制御モードを指定します。
 - **Enqueue.ISGENQ_CONTROL_SHARED**　共有モード（読み込み処理の場合に指定）
 - **Enqueue.ISGENQ_CONTROL_EXCLUSIVE**　排他モード（書き込み処理の場合に指定）
- リソース競合発生時に待たせる場合は、Enqueue#setContentionActWaitをコールします。
- Enqueue#obtainメソッドでエンキューします。
- Enqueue#releaseメソッドでデキューします。

**注2)**
上記サンプルは、スタンドアロンJavaアプリケーションとして動かすために、スコープ指定を**Enqueue.ISGENQ_SCOPE_SYSTEM**にしていますが、WAS for z/OS環境で同一JVM内の複数スレッド間の競合を制御する場合は、**Enqueue.ISGENQ_STEP**をスコープに指定します。

**注3)**
APIの詳細については[参考リンク2](https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.api.80.doc/com.ibm.jzos/com/ibm/jzos/Enqueue.html)を参照してください。また、当該APIがコールするシステム・サービスについては、[参考リンク3](https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.ieag400/comm.htm)、[参考リンク4](https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.ieaa900/isgenq.htm)、[参考リンク5](https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.ieaa200/isgenq.htm)を参照してください。

# 参考リンク
**1. JZOS Batch Launcher and Toolkit**
https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.80.doc/zsecurity-component/jzos.html

**2. JZOS API (Javadoc)**
http://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.api.80.doc/com.ibm.jzos/index.htm

*com.ibm.jzos.EnqueueクラスのJavadoc*
https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.api.80.doc/com.ibm.jzos/com/ibm/jzos/Enqueue.html

**3. グローバル・リソース逐次化**
https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.ieag400/comm.htm

**4. ISGENQ — グローバル・リソース逐次化 ENQ サービス**
https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.ieaa900/isgenq.htm

**5. ISGENQ — Global resource serialization ENQ service（上記の英文ページ）**
https://www.ibm.com/support/knowledgecenter/ja/SSLTBW_2.3.0/com.ibm.zos.v2r3.ieaa200/isgenq.htm

**6. JZOSを使ってJavaからデータセットにアクセスしてみる**
https://qiita.com/tsunogai/items/8f75ce6a2f52c6c61ff5