alphaのjava備忘録

alphaが勉強したことを書いていくブログ

サーブレットを作成する

サーブレットを使えるようになるため、まずは簡単なサーブレットを作ってみる。ずっと誤解してたけどサーブレットってJSPを用意しなくてもが単体で動くのね。
参考:Eclipseを使ったサーブレット用プロジェクトの作成方法は↓に分かり易く載ってる。
qiita.com


サーブレット用のプロジェクトをEclipseで作成
・プロジェクト作成
   「ファイル」タブ⇒「新規」⇒「その他」⇒「Web」⇒「動的Webプロジェクト」で作成。(「Test」プロジェクト)
・パッケージ作成
   「src」配下で「ファイル」タブ⇒「新規」⇒「パッケージ」⇒で作成。(「servletTest」)
サーブレット作成
   「src」-「servletTest」配下で「ファイル」タブ⇒「新規」⇒「その他」⇒「Web」⇒「サーブレット」で作成。(「Servlet1.java」)

f:id:dream_of_night:20180706225245p:plain



サーブレットのプログラムはこちら。
Servlet1.java

package Test;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/Servlet1")
public class Servlet1 extends HttpServlet {

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.getWriter().append("ServletTest1!");
	}
}

(1)Servlet1クラスはHttpServletクラス(Web サイトに適した HTTP Servlet を生成するメソッドを持つ。doGet()メソッドもその1つ)を継承。
(2)Eclipseの「実行」を押すとサーバー(Tomcat)がリクエストを送りdoGet()メソッド(の中に書かれた処理)が呼ばれる。
(3)response.getWriter()メソッドはPrintWriterクラスのオブジェクトを戻り値で返す。
(4)PrintWriterクラスのメソッドappend()で "ServletTest1!" が(3)のオブジェクトに追加される。
(5)画面が表示される。 

f:id:dream_of_night:20180706235134p:plain
それっぽく書いたけどまだいろいろ曖昧なところがあるので理解間違ってたらご指摘ください。

サーブレット/JSPとは

JavaでWEBアプリを作るためにはサーブレット/JSPの技術が必要ということで勉強中。
今日分かってる範囲で知識整理

・サーブレットコンテナ(ex Tomcat):いわゆるサーバ
・サーブレット                                :サーバ上で動くプログラム
・JSP                                                :出力画面を作成

サーブレットでも画面は作成できるけど、JSPで書いた方が分かり易く簡単らしい。

スレッドセーフでないコードを書くには

昨日書いたソースをいじっていたら偶然にもスレッドセーフでない(簡単な)ソースコードが生まれたので記録しておく。ネットでスレッドセーフって調べると上級者向けのソースコードばかりヒットするからこういう簡単なのがあってもいいよね。。。

あるコードがスレッドセーフであるという場合、そのコードを複数のスレッドが同時並行的に実行しても問題が発生しないことを意味する。 特に、ある共有データへの複数のスレッドによるアクセスがあるとき、一度に1つのスレッドのみがその共有データにアクセスするようにして安全性を確保しなければならない。

スレッドセーフ - Wikipedia


複数のスレッドを立てる処理を行う場合、意図しない順序でスレッドが動くことがある。生成された各スレッドは独立して動くので順序関係とか守ってくれないのだ。そのため値の加算や代入が処理に絡む場合、値や出力がおかしくなることがある。これがスレッドセーフでないプログラムである。実際にスレッドセーフでないソースコードを書いてみた。

public class Main {
	public static void main(String[] args){
		Thread[] th = new Thread[100];
		for(int i=0;i<100;i++){
		th[i] = new Thread1();
		}

		for(Thread t:th){
			t.start();
		}
	}

}

class Thread1 extends Thread{
	static int cnt=1;
	public void run(){
		System.out.println("Thread "+cnt+" moved.");
		cnt++;
	}
}

ポイントはThread1クラスの変数であるcnt。staticな変数であるため、Thread1クラスのインスタンスがstart()する度にcnt++が実行された値が増えていくというわけ。通常通りに動けば出力される値も1ずつ変わっていく。

このソースの処理は通常なら以下のように進むはず。
(1) Thread型配列thを定義。要素数=100。
(2) for文で 100回Thread1クラスのインスタンスをnew。
(3) Thread1クラスのインスタンスをth[i] に格納。
(4) 拡張for文で配列thに含まれる100個のインスタンスでstart()メソッドを実行。
(5) "Thread 1 moved." "Thread 2 moved." ~"Thread 100 moved." がコンソールに出力される(はず)。


実行結果(7行目まで抜粋)
Thread 1 moved.
Thread 1 moved.
Thread 1 moved.
Thread 2 moved.
Thread 5 moved.
Thread 5 moved.
Thread 5 moved.
(以下省略)

異常が発生する。"Thread 1 moved." の次の出力は "Thread 2 moved." になるべきなのに"Thread 1 moved." が続いている。おまけに"Thread 2 moved." の次は "Thread 5 moved." になっている。スレッドセーフでないコードによって出力に異常が起きたことが確認できる。



ソースコードの何が異常の原因なんだろう?100回インスタンス化されたThread1クラスを見ると原因が分かる。

class Thread1 extends Thread{
	static int cnt=1;
	public void run(){
		System.out.println("Thread "+cnt+" moved.");
		cnt++;
	}
}

このクラスのインスタンスを実行するとrun()の中の処理が(1)⇒(2)の順で実行される。
(1) System.out.println("Thread "+cnt+" moved."); 
(2) cnt++

複数スレッドがある場合、スレッドが順次動けば処理は次のようになる。

Thread1(1)
(1) System.out.println("Thread 1 moved."); 
(2) cnt++
Thread1(2)
(1) System.out.println("Thread 2 moved."); 
(2) cnt++
Thread1(3)
(1) System.out.println("Thread 3 moved."); 
(2) cnt++
Thread1(4)
(1) System.out.println("Thread 4 moved."); 
(2) cnt++
......(以下略)


しかし実際には複数のスレッドは独立に動くので処理は次のようになった。
出力⇒cnt++の順序が崩れてしまっている。

Thread1(1)
(1) System.out.println("Thread 1 moved."); 
Thread1(2)
(1) System.out.println("Thread 1 moved."); 
Thread1(3)
(1) System.out.println("Thread 1 moved."); 
Thread1(4)
(1) System.out.println("Thread 2 moved."); 
(2) cnt++
(2) cnt++
(2) cnt++
Thread1(5)
(1) System.out.println("Thread 5 moved."); 
......(以下略)

このようにスレッド内に複数の処理がある場合、その順序が守られないことがある。だから、スレッド内に複数の処理を書いて、かつ値の加減・変更・代入などの処理が含まれる場合、スレッドセーフでないコードを書くことが出来る。気を付けよう!




余談。
ちなみにもう一回同じソースを実行すると、出力値が変わる。複数のスレッドの動きは毎回変わるんだね~。これ異常が発見できない動き方をすることもあるんだから怖いね。

実行結果(7行目まで抜粋)
Thread 1 moved.	
Thread 1 moved.	
Thread 1 moved.	
Thread 1 moved.	
Thread 1 moved.	
Thread 4 moved.	
Thread 6 moved.
(以下省略)

Threadの配列を作るには

Threadを大量に作成するにはどうしたらいいだろう?今まではスレッドを作成するごとにインスタンスをnewしてきた。この方法だと10個のスレッドを使用する場合、10回newを書く必要がある。変数も10個用意してstart()メソッドも10回書く必要がある。

class Thread1 extends Thread{
	public void run(){
		System.out.println("Thread 1 moved.");
	}
}

class Main{
	public static void main(String[] args){
		Thread1 th1 = new Thread1();
		th1.start();
	}
}


多くのスレッドを立てて並列処理を行うことこそがThreadを使う理由である。となるとこれ以外にも沢山のスレッドを一度に作成する方法があった方がいい。それで今回は配列を使用してThreadを扱う方法を学ぼうと思う。具体的には上記のスレッドを100回立てるソースを書いてみる。


ポイント

  ・Thread型の配列を事前に必要スレッド数だけ宣言する。
  ・Thread型の配列への値の格納、及び格納するインスタンスのnewはfor文中で行う。(×100回)
  ・100個作成したインスタンスは100回start()メソッドを実行する必要がある。
  ・start()メソッドも別途for文を書いてfor文中で行う。
    (こちらはThread配列を繰返処理するので拡張for文でOK)
class Thread1 extends Thread{
	public void run(){
		System.out.println("Thread 1 moved.");
	}
}

class Main{
	public static void main(String[] args){
		Thread[] th = new Thread[100];
		for(int i=0;i<100;i++){
		th[i] = new Thread1();
		}		
		for(Thread t:th){
			t.start();
		}
	}
}

(1) Thread型配列thを定義。要素数=100。
(2) for文で 100回Thread1クラスのインスタンスをnew。
(3) Thread1クラスのインスタンスをth[i] に格納。
(4) 拡張for文で配列thに含まれる100個のインスタンスでstart()メソッドを実行。
(5) 100回分の"Thread 1 moved." がコンソールに出力される。

実行結果
Thread 1 moved.
Thread 1 moved.
Thread 1 moved.
Thread 1 moved.
Thread 1 moved.
Thread 1 moved.
(以下100個目の出力まで略)

無事100個スレッドを実行できた。

スレッドセーフでないコードで異常を起こすには

ここのところThreadを使用する方法について勉強しててイマイチ理解できなかったのが「スレッドセーフ」という言葉。

あるコードがスレッドセーフであるという場合、そのコードを複数のスレッドが同時並行的に実行しても問題が発生しないことを意味する。 特に、ある共有データへの複数のスレッドによるアクセスがあるとき、一度に1つのスレッドのみがその共有データにアクセスするようにして安全性を確保しなければならない。

スレッドセーフ - Wikipedia

つまりスレッドセーフでないコードを書いて複数のスレッドを使用すると、値や出力に異常が起こる問題が起こる「かも」しれないらしい。と言っても正直実例がないとよく分からない。。。なので今回は敢えてスレッドセーフ「でない」コードを書いて異常を起こしてみることにする。

StringBuilderであれが発生するみたいね
ちなみに参考にしたのがこの記事!ほぼ同じ内容だけど....まだ応用を書けるほどの理解はないのじゃ、、、
totech.hateblo.jp

MainクラスでRunnableインターフェイスを実装するには

短記事。先日書いた記事のRunnable版。タイトルの通りRunnableインターフェイスをmain()メソッドのあるクラスに実装してスレッドを生成できるか試したい。同じような記事だけどネタ切れではない。まだ勉強してないこと沢山あるしね。

実践したソースコードは以下の通り。

class Main implements Runnable
{
	public static void main (String[] args)
	{
		System.out.println("Thread 1 moved.");
		Main   mi = new Main();
		Thread th = new Thread(mi);
		th.start();
	}
	
	public void run(){
		System.out.println("Thread 2 moved.");
	}
}
実行結果
Thread 1 moved.
Thread 2 moved.

ところでThreadの勉強を始めたときから何故Threadクラスは何のimportも無しに使えるのか疑問だったんだけど、調べてみたらThreadクラスは Java.langパッケージのクラスなんだね。正式名称(完全限定名というらしい)はJava.lang.Thread 。で、Java.langパッケージはデフォルトでimportされてるらしい。StringクラスやSystem.out.println()メソッドで多用するSystemクラスも含まれるらしい。Java.lang.StringやJava.lang.Systemなのね。はえー勉強になるー。



stackoverflow.com
見栄を張るために英語の参考記事を貼ってみたり。

implementsを使ってThreadを実装するには

前々回の記事では、Javaでのスレッドの実装方法は2種類あるという話だった。

Javaでスレッドを使用する方法には
 ・extendsを使う
 ・implementsを使う
の2種類がある。今回は簡単そうなExtendsの方を実践してみる。

今回はまだ触れてないimplementsを使う方法でスレッドを実装してみる。


extendsを使う方法との違いは

 ・スレッドとして使用するクラスでextendsの代わりにimplementsを使う。
   ・Threadクラスを継承する代わりに、Runnableインターフェイスを実装
   ・継承しないため、別のクラスの継承をしてもOK

 ・スレッドとして使用するクラスをインスタンス化した後、直接start()メソッドを使用できない。
   ・↑のインスタンスを引数にThreadクラスのインスタンスを生成して、
    Threadクラスのインスタンスからstart()メソッドを実行しなければならない。
   ・つまりスレッド実装には、結局Threadクラスのインスタンスとstart()メソッドが必要。

extendsを使う方法と同じ点は

 ・Runnableインターフェイスを実装したクラスでは
  並列処理したい内容をrun()メソッド内に記載する必要がある
 ・スレッドを使用するクラスではそれをstart()メソッドで実行する必要がある


以下が実践してみたソースコード

class Main
{
	public static void main (String[] args)
	{
		System.out.println("Thread 1 moved.");
		Thread2 th2 = new Thread2();
		Thread  th  = new Thread(th2);
		th.start();
	}
}

class Thread2 implements Runnable{
	public void run(){
		System.out.println("Thread 2 moved.");
	}
}
実行結果
Thread 1 moved.
Thread 2 moved.

extendsを使う方法との一番の違いはやっぱりRunnableインターフェイス実装クラスをインスタンス後に、さらにそのインスタンスを引数にThreadクラスのインスタンスを作成する必要があるところかな。