自分の勉強用のメモ。
メモリ領域
変数の値、生成されたインスタンス、呼び出されたメソッドの履歴などを格納する領域。
これらをどこに格納するかは、JVMによって管理している。
メモリ領域には2種類あり
- スタック
- ヒープ
がある。
スタック
メソッドの呼び出し履歴、メソッド内で宣言された変数(ローカル変数)の値。
メソッドの処理が終わると、その情報はスタックから削除される。
ヒープ
クラスから生成されたインスタンス。
インスタンスが、どの変数からも参照されなくなったとき(参照先の変更、nullの代入)
に、JVMのガーベッジコレクションで自動で開放される。
スレッド
以下の2種類がある。
- シングルスレッド
- マルチスレッド
マルチスレッドを作成する
方法は2つ
- Threadクラスを継承した新しいクラスを作る
- Runnableインターフェースを実装した新しいクラスを作る。
Threadクラスで作る
Threadクラスを継承したクラスのインスタンスを生成して、
そのインスタンスのstartメソッドを呼ぶ。
実行するのは、startメソッドだけど、
Threadクラスを継承したサブクラスでは、
Threadクラスが持つrunメソッドをオーバライドする。
startメソッドを経由して、runメソッドが実行される。
結果は必ず同じではなく、実行されるたびに変わる(変わらない時もある)
class MyTread extends Thread{
//メモ:スーパークラスで定義されているアクセス修飾子より、レベルを下げることはできない
//Thread から継承されたメソッドの可視性を下げることはできません
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("MyTread.run:"+i);
}
}
}
public class SampleProgram{
public static void main(String[] args) {
MyTread mt = new MyTread();
mt.start();
for(int i = 0; i < 5; i++) {
System.out.println("main:"+i);
}
}
}
//結果
main:0
main:1
main:2
MyTread.run:0
MyTread.run:1
MyTread.run:2
MyTread.run:3
main:3
MyTread.run:4
main:4
Runnableインタフェースで作る
継承して作った時とあんまり変わらない。
class MyTread implements Runnable{
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("MyTread.run:"+i);
}
}
}
public class SampleProgram{
public static void main(String[] args) {
MyTread mt = new MyTread();
Thread t = new Thread(mt);
t.start();
for(int i = 0; i < 5; i++) {
System.out.println("main:"+i);
}
}
}
Thread.sleep(ミリ秒(=1000分の1秒))
スレッドの処理を一定時間止める。
単位は、ミリ秒。
1000:1秒
2000:2秒
マイクロ秒だと
1000000:1秒
Thread.sleepメソッドは、InterruptedException型の例外を投げる可能性があるので
catchしないといけない。
public class SampleProgram{
public static void main(String[] args) {
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:"+i);
}
}
}
// Runnableインターフェースを実装したほうで試してみた
class MyTread implements Runnable {
public void run() {
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
System.out.println("MyTread.run:"+i);
}
}
}
public class SampleProgram{
public static void main(String[] args) {
MyTread mt = new MyTread();
Thread t = new Thread(mt);
t.start();
for(int i = 0; i < 5; i++) {
System.out.println("main:"+i);
}
}
}
join()
スレッドの処理を待つことができる。
joinメソッドは、InterruptedException型の例外を投げる可能性があるので
catchしないといけない。
MyTreadクラスの処理がすべて終わってから、mainの処理が実行されている。
class MyTread implements Runnable {
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("MyTread.run:"+i);
}
}
}
public class SampleProgram{
//今度は、throwsしてみる。
public static void main(String[] args) throws InterruptedException {
MyTread mt = new MyTread();
Thread t = new Thread(mt);
t.start();
t.join();
for(int i = 0; i < 5; i++) {
System.out.println("main:"+i);
}
}
}
スレッドを止める
runメソッドの処理を終了させれば、そのスレッドの処理は終了する。
うーん、、、、今は置いておこう。
同期をとる。
マルチスレッドで動く場合、複数のスレッドで同じ変数の値を共有すると
不整合が生じることがある。
以下は、合計金額が1000000円になる予定のプログラム
class Bank{
static int money = 0;
static void addOneYen() {
money++;
}
}
class Customer extends Thread{
public void run() {
for(int i = 0; i < 10000; i++) {
Bank.addOneYen();
}
}
}
public class MultiThreadEx {
public static void main(String[] args) {
Customer[] customers = new Customer[100];
for(int i = 0; i < 100; i++) {
customers[i] = new Customer();
customers[i].start();
}
for(int i = 0; i < 100; i++) {
try {
customers[i].join();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Bank.money["+Bank.money+"]");
}
}
//結果
Bank.money[628887]
現在の金額を参照・更新するタイミングで、各スレット間で整合性がとれないため、
合計金額がおかしくなる。
そんな時は、addOneYenメソッドの前に「synchronized」を付ける。
static synchronized void addOneYen() {
money++;
}
synchronizedをメソッドの前につけると、1度に1つのスレッドしか実行できない。
あるスレットが処理を行っているときは、ほかのスレットは自分の番まで待機する。