技術の犬小屋

Webプログラミングを餌に生きる犬のメモ帳

Posts in the Java category

JavaにもPHPのcloneメソッドのような機能があり、PHPのcloneメソッドのような使い勝手を期待したが、あまり良いものではなかった。使い方が結構特殊だったのでメモを残しておく。
 

2種類のコピー方法

オブジェクトのコピーにはシャローコピーとディープコピーの2種類があり、cloneメソッドの使い方を学ぶ上では、前提知識として持っておくべきである。以下より、それぞれのコピーについて説明する。
 

シャローコピー

シャローコピー(浅いコピー)は、コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同じデータ(インスタンス変数)を参照している状態である。
コピー元のオブジェクトに対してインスタンス変数に変更を加えると、コピー先のオブジェクトが参照しているデータが同じ物なので、コピー先のオブジェクトから見たインスタンス変数も変更されることになる。
つまり、シャローコピーは参照のコピーのみを行う。
 

ディープコピー

ディープコピー(深いコピー)は、オブジェクトのみのコピーではなく、オブジェクトとメモリ上のデータ(インスタンス変数)の両方をコピーする。
二つのオブジェクトが参照しているデータは別々のものなので、一方のオブジェクトのインスタンス変数に変更を加えても、もう一方のオブジェクトには影響を与えない。
 

cloneメソッドとは

cloneメソッドとは、オブジェクトのコピーを返すメソッドである。Javaのcloneメソッドはかなり特殊で、Java以外の言語(PHPやRubyなど)のcloneメソッドではディープコピーを提供するが、Javaのcloneメソッドでは、シャローコピーが提供される。しかし、メソッドの使用者がcloneメソッドに対して期待する動作はディープコピーであるため、Javaのcloneメソッドでは、不変オブジェクト(状態が変更されないオブジェクト)のみをシャローコピーとして提供し、可変オブジェクト(状態が変更されるオブジェクト)をディープコピーとして提供出来るように書き換える必要がある。
 

Javaのcloneメソッドの使い方

Cloneableインターフェースは、Javaの中では異端なインターフェースである。Cloneableインターフェースは、メソッドを含んでいないにも関わらず、Object.clone()のオーバーライドを必要とする(全てのクラスは暗黙的にObjectクラスを継承している)。逆に、Object.clone()は、継承したクラスがCloneableインターフェースを実装(implements)していなければ、実行できず、CloneNotSupportedExceptionをスローする。
 

cloneメソッドを実装する際のルール

  • Cloneableインターフェースを実装(implements)する。
  • Object.clone()をオーバーライドし、cloneメソッドをpublic、もしくはprotectedで宣言する。
  • cloneメソッドにより生成されたオブジェクトではコンストラクタが実行されない。
  • Cloneableインターフェースを実装するクラスのフィールドが、不変オブジェクト(プリミティブ型、String型、またはプリミティブ型のラッパークラス型)の場合は、cloneメソッドの中身はsuper.clone()を呼び出して、その戻り値を返すだけの単純な実装になる。
  • Cloneableインターフェースを実装するクラスのフィールドが可変オブジェクト(配列、コレクション、クラス型)を持つ場合は、そのフィールドを複製する実装をcloneメソッドに記述し、更にsuper.clone()を呼び出し、それぞれの戻り値を一緒に返す必要がある。

 
1つ目~3つ目のルールについては、書いてある通りなのでこれ以上の説明は省くとして、注目して欲しいので4つ目と5つ目である。
 
4つ目では、不変オブジェクトのみを扱う場合は、シャローコピーのみを行う。その理由は、不変オブジェクトは内部の状態が変わることが無いので、ディープコピーを行う必要が無いためである。
 
5つ目では、可変オブジェクトを扱う場合は、そのオブジェクトのディープコピーを行う。その理由は、可変オブジェクトをシャローコピーすると、コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同じデータを参照するため、予想が付かない振る舞いを引き起こしかねないからである。
 

cloneメソッドの実装例

クラスに不変オブジェクトしかない場合

public class DataBox implements Cloneable {
    private int historyNO = 0;
    private Integer value = 0;
    private String data   = "a";

    @Override
    public DataBox clone() {
        try {
            return (DataBox)super.clone();
        } catch() {
            throw new AssertionError();
        }
    }
}

 

クラスに可変オブジェクトがある場合

public class SmokyDog implements Cloneable {
    private String name = "SmokyDog";
    private Date birthDate;

    private setBirthDate(Date b) {
        this.birthDate = b;
    }

    @Override
    public SmokyDog clone() {
        try {
            SmokyDog result = (SmokyDog)super.clone();
            //ディープコピー
            result.setBirthDate(new Date(this.birthDate.getTime()));
            return result;
        } catch() {
            throw new AssertionError();
        }
    }
}

 
 
Javaでは、Cloneableを実装する以外にもオブジェクトのコピーを作成する方法がある。以下の2つがそうなのだが、長くなりそうなので今回は紹介だけに留める。

  • コピーコンストラクタ
  • コピーファクトリー

 
 
以上
 
 
参考
Object (Java Platform SE 8 )
書籍 Effective Java
最新Javaコーディング作法  プロが知るべき、107の規約と21の心得
Javaにおけるオブジェクトの複製(clone) – Object#cloneのオーバーライド
シャローコピーとディープコピーの違い – くろの雑記帳

Effective Javaを読んでいたところ、勉強になったことがあったのでメモしておく。
 

コンポジションとは

コンポジション(Composition)は、日本語で「混合物」を意味する単語である。あるクラスの機能を持つクラスのことを指す。
特定のクラスの機能を、自分が作るクラスにも持たせたい場合に、継承を使わずフィールドとしてそのクラスを持ち、そのクラスのメソッドを呼び出すメソッドを持たせること。そうすることで、クラスに他のクラスの機能を組み込むことができる。
厳密なオブジェクト指向では、継承は「機能の継承」を目的とせず、「スーパークラスはサブクラスの一種である」といういわゆる「is-a」の関係を持たなければならない。そのため、単に機能を持たせたい場合には、継承ではなくコンポジションとすることが推奨される。
 

継承とコンポジションをどう使い分けるか

継承を使ったほうが良い場合とコンポジションを使ったほうが良い場合があるが、具体的に何を基準にして使い分ければ良いだろうか。
この問題を解決するために、一般的な物の考え方の一つに「分類」と「分割」がある。
 
分類とは、A is a B.のことであり、日本語だと「AはBである」と表現できる。これをis-aの関係と呼ぶ。
例えば、「犬は動物である」「猫は動物である」「人間は動物である」・・・など、これらは分類で表現することが出来る。
is-aの関係で表せるものは、継承で表現するのが都合がいい。
 
分割とは、A has a B.のことであり、日本語だと「AはBを含んでいる」と表現できる。これをhas-aの関係と呼ぶ。
例えば、「パソコンはCPUを含んでいる」「学校は先生を含んでいる」「自転車はサドルを含んでいる」・・・など、これらは分割で表現することが出来る。
has-aの関係で表せるものは、コンポジションで表現するのが都合がいい。
 

継承とコンポジションの違い

ここで改めて、継承とコンポジションの違いを明確にするために、コードを例に挙げて説明する。
まず、親となるPersonクラスを定義する。Personクラスは「氏名」と「年齢」を保持し、要求に応じてそれを返す。

public class Person {
    private String name;
    private int age;

    public Person(String n, int a) {
        name = n;
        age  = a;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

 
ここでPersonクラスを元にして、新たに「学年(grade)」を保持し、要求に応じてそれを返すことが出来るクラスを作りたい場合を考えてみる。
以下より、継承とコンポジション、それぞれを使用した場合の違いをコードで説明する。
 

継承を使った場合のコード

public class Student extends Person {
    private int grade;

    public Student(String n, int a, int g) {
        super(n, a);
        grade = g;
    }

    public int getGrade() {
        return grade;
    }
}
Student yamada = new Student("山田", 20, 2);
String name = yamada.getName();
int age     = yamada.getAge();
int grade   = yamada.getGrade();

 

コンポジションを使った場合のコード

public class Student {
    private Person person;
    private int grade;

    public Student(Person p, int g) {
        person = p;
        grade  = g;
    }

    public String getName() {
        return person.getName();
    }

    public int getAge() {
        return person.getAge();
    }

    public int getGrade() {
        return grade;
    }
}
Person y       = new Person("山田", 20, 2);
Student yamada = new Student(y, 2);
String name = yamada.getName();
int age     = yamada.getAge();
int grade   = yamada.getGrade();

 
上記の例では、継承とコンポジション、どちらを使っても自然に記述することが可能だが「継承とコンポジションをどう使い分けるか」の項目で説明した通り、どちらか一方では上手く記述できない場合がある。そういったときは、一度立ち止まってis-aとhas-aのどちらに当てはまるかを考えてみることが必要だ。
 
書籍「Effective Java」の「項目16 継承よりコンポジションを選ぶ」には、今回載せたコンポジションのコードよりも頑強な設計のコードが載っている。しかし、少々理解し辛い部分があったので、富士通さんの記事に載っていたコードを引用させていただいて説明した。気になった方はEffective Javaを読んでみると良いと思う。
 
 
以上
 
 
参考
書籍 Effective Java
コンポジションとは : JavaA2Z
オブジェクト指向プログラミングへの道 5日目:オブジェクトコンポジション : 富士通
オブジェクト思考: is-a関係とhas-a関係: 継承と包含
is-a – Wikipedia
has-a – Wikipedia

Javaでマルチスレッドプログラミングを覚えようと、こちらのサイトを読んで写経してみた。前回の記事に引き続き、今回はスレッドの同期について。
 

他のスレッドの終了を待つ

join()

別のスレッドにある処理を任せ、そのスレッドが終了したときに、自分のスレッドの処理を再開したいという場面がある。このようなとき、Threadクラスのjoinメソッドを利用する。
 

void join();
void join(long millis)
void join(long millis, int nanos)

 
join()は該当のスレッドが終了するのを待ちつづける。引数のあるものは、該当のスレッドが終了しなくても、指定した時間経過すれば処理を再開する。いずれのメソッドも、外部のスレッドよりinterruptメソッドで割り込まれた場合、InterruptedExceptionを発生して処理を終了する。
 

public class JoinTest extends Thread {
    public void run() {
        for (int i = 3; i >= 0 ; i--) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {}
            System.out.println(i);
        }
    }
 
    public static void main(String[] args) {
        JoinTest t = new JoinTest();
        t.start();
        try {
            System.out.println("スレッド t の終了を待機します。");
            t.join(); // カウントダウンが終了するのを待つ
            System.out.println("スレッド t が終了しました。");
        } catch (InterruptedException e) {}
    }
}

 
この例では以下のような出力が行われる。

スレッド t の終了を待機します。
3
2
1
0
スレッド t が終了しました。

 

スレッド間の待ち合わせ

複数のスレッドが協調して処理を行う場合、あるスレッドの部分処理が終了するのを、もう一方のスレッドが待機したい場合がある。そのようなとき、java.lang.Objectクラスのwait、notify、notifyAllメソッドを使用する。
 

wait()

waitメソッドは、notifyまたはnotifyAllメソッドが呼び出されるまで処理を待機するメソッドである。waitメソッドを呼び出すためには、スレッドは該当するオブジェクトのロックを取得していなければならない。
 

notify()、notifyAll()

notifyおよびnotifyAllメソッドは、waitメソッドによって待機状態にあるスレッドの実行を再開させるメソッドである。あるオブジェクトに関して、waitメソッドで待機しているスレッドの集合のことを「ウェイトセット」と呼ぶ。
 
notifyメソッドは、ウェイトセット内の1つのスレッドの処理を再開させる。再開されるスレッドは、Java仮想マシンによって任意に選ばれ、プログラムで指定することはできない。
 
notifyAllメソッドは、ウェイトセット内の全てのスレッドの処理を再開させる。notifyおよびnotifyAllメソッドを実行するためには、waitメソッドの場合と同じく、該当オブジェクトのロックを取得していなければならない。
 

例として、wait()及びnotifyAll()を使ってスレッド間の待ち合わせを行う処理を考えてみる。
 

今回登場するクラス
  • QueueTestクラス – mainメソッドが記述されたクラス。Producer、Consumerクラスのインスタンスを作成する。
  • Queueクラス – キューを管理するクラス。ゲッター(getter)とセッター(setter)を持つ。
  • Producerクラス – 数字を生産(Produce)し、キューへと格納するクラス。Queueクラスのセッターを呼び出す。
  • Consumer – 数字を消費(Consume)するためにキューから取り出すクラス。Queueクラスのゲッターを呼び出す。

 

プログラムの概要

Queueクラスで管理されているキュー(queue変数)には、最大で3つまでの数字を格納することが出来る。
Producerクラスがキューへ数字を格納し、Consumerクラスがキューから数字を取り出す。
もし、キューの中に3つ数字が格納されていて、これ以上格納できないのであれば、Producerクラスはwait()を呼び出す。
もし、キューの中に1つも数字が格納されていないのであれば、Consumerクラスはwait()を呼び出す。
Producerクラスがwait()した場合、Consumerクラスによってキューに格納された数字が2個以下になるまで取り出され、notifyAll()が呼び出されるまで待機する。
Consumerクラスがwait()した場合、Producerクラスによってキューに数字が1つ以上格納され、notifyAll()が呼び出されるまで待機する。
 

import java.util.LinkedList;
 import java.util.Iterator;
  
 public class QueueTest {
   public static void main(String[] args) {
     Queue queue = new Queue();
     new Producer(queue).start();
     new Consumer(queue).start();
   }
}
 
class Queue {
  LinkedList queue;
  public Queue() {
    queue = new LinkedList();
  }
  synchronized public void put(Object obj) {
    while (queue.size() >= 3) {
      System.out.println(obj + "を追加しようとしましたが,"
                         + "キューがいっぱいなので待機します。");
      try {
        wait();
      } catch (InterruptedException e) {}
    }
    queue.addFirst(obj);
    printList();
    System.out.println(obj + "をキューに追加しました。");
    notifyAll();
  }
  synchronized public Object get() {
    while (queue.size() == 0) {
      System.out.println("データを取り出そうとしましたが,"
                         + "キューが空なので待機します。");
      try {
        wait();
      } catch (InterruptedException e) {}
    }
    Object obj = queue.removeLast();
    printList();
    System.out.println(obj + "をキューから取り出しました。");
    notifyAll();
    return obj;
  }
  synchronized public void printList() {
    System.out.print("[ ");
    for (Iterator i = queue.iterator(); i.hasNext();) {
      System.out.print(i.next() + " ");
    }
    System.out.print("]  ");
  }
}
 
class Producer extends Thread {
  private Queue queue;
  public Producer(Queue queue) {
    this.queue = queue;
  }
  public void run() {
    for (int i = 0; i < 100; i++) {
      try {
        Thread.sleep((long)(Math.random()*1000));
      } catch (InterruptedException e) {}
      queue.put(new Integer(i));
    }
  }
}
 
class Consumer extends Thread {
  private Queue queue;
  public Consumer(Queue queue) {
    this.queue = queue;
  }
  public void run() {
    for (int i = 0; i < 100; i++) {
      try {
        Thread.sleep((long)(Math.random()*1000));
      } catch (InterruptedException e) {}
      Object obj = queue.get();
    }
  }
}

 

実行結果
[ 0 ] 0をキューに追加しました。
[ ] 0をキューから取り出しました。
データを取り出そうとしましたが,キューが空なので待機します。
[ 1 ] 1をキューに追加しました。 
[ ] 1をキューから取り出しました。
[ 2 ] 2をキューに追加しました。
[ ] 2をキューから取り出しました。
[ 3 ] 3をキューに追加しました。
[ 4 3 ] 4をキューに追加しました。
[ 5 4 3 ] 5をキューに追加しました。
6を追加しようとしましたが,キューがいっぱいなので待機します。
[ 5 4 ] 3をキューから取り出しました。
[ 6 5 4 ] 6をキューに追加しました。

 
 
以上
 
 
参考
マルチスレッドプログラミング | TECHSCORE(テックスコア)

Javaでマルチスレッドプログラミングを覚えようと、こちらのサイトを読んで写経してみた。前回の記事に引き続き、今回はスレッドの優先度・休止・中断について。
 

スレッドの優先度

すべてのスレッドは優先度を持っている。資源が競合した際、高い優先度を持つスレッドの方が優先的に実行される。スレッドの優先度はgetPriorityメソッドで取得する事ができる。
優先度を設定するにはsetPriorityメソッドを使う。引数に優先度を表す数字を指定する。この値は「Thread.MAX_PRIORITY」「Thread.MIN_PRIORITY」というクラス変数で定義されている最小値(1)から最大値(10)の範囲に収まるものでなければならない。なおデフォルトで与えられる優先度は「5」であり、この値は「Thread.NORM_PRIORITY」クラス変数に定義されている。
しかし、優先度の違いによる資源の割り当て方は、OSやJava仮想マシンの実装に大きく依存する。そのため、高い優先度を設定すると、ある程度は優先的に実行されるかもしれないが、もしかしたら実際には全く優先されないかもしれない。
 

スレッドの休止と中断

ここでは、スレッドの処理を一時休止したり、他のスレッドに割り込みをかけるためのメソッドを説明する。
 

sleep()

sleepメソッドはThreadクラスのクラス(static)メソッドである。sleepメソッドは指定した時間だけ現在実行中のスレッドを休止させる。sleepメソッドはクラスメソッドのため、sleepメソッドを実行するスレッド以外の別のスレッドを休止することはできない。
 
sleepメソッドでの時間の指定方法は以下の2種類となる。

sleep(long millis);
sleep(long millis, int nanos);

 
sleepメソッドを呼び出したスレッドは、取得しているロックを解放しない。ロックを確保したままsleepを実行すると、そのロックを取得したいスレッドを必要以上に待たせてしまうおそれがある。ロックを解放した状態でスレッドを休止させたいときは、waitメソッドを利用することを検討する。
 
スレッドは、sleepメソッドで指定した時間経過しても直ちに動作を再開するわけではない。指定した時間経過後、動作可能な状態にはなるが、他のスレッドがなにか処理を実行中の場合、そのスレッドの実行が継続される。
 
sleepメソッドは、休止中に他のメソッドから割り込みがかけられたときにInterruptedExceptionが発生するので、例外処理を記述しなければならない。割り込みは、あとで説明するinterruptメソッドによって発生させることができる。
 

yield()

yieldメソッドは、現在処理中のスレッドを一時休止し、他のスレッドに実行の機会を与える。Java仮想マシンが複数のスレッドにどのように時間を割り当てるかというのは、実装によって様々である。実装によっては、他にも実行可能なスレッドがあるにもかかわらず、あるスレッドからなかなか処理が移らないということがある。そのようなことを防ぐためにyieldメソッドを用いる。
 

interrupt()

interruptメソッドは休止中のスレッドに割り込みを入れるメソッドである。割り込みを入れることのできるスレッドは、joinメソッドやsleepメソッドの実行により待機中のメソッド、Objectクラスのwaitメソッドで待機中のメソッドである。
 
割り込みを入れられたメソッドは、java.lang.InterruptedException例外を発生し、処理を再開する。
 

使用を推奨されないメソッド

stop()

スレッドを強制的に停止させるメソッドである。スレッドがどのような状態にあってもスレッドを停止させるため、そのスレッドが操作中のオブジェクトのデータに不整合が生じるおそれがある。
 

suspend()

スレッドを直ちに中断させるメソッドである。中断されたメソッドは次のresume()メソッドによって再開される。suspendメソッドは、スレッドが取得しているロックを解放しないため、デッドロックの原因となりやすいという問題がある。
なお、デッドロックとは、複数のスレッドがオブジェクトのロックを確保できない状態で処理が進まなくなってしまう状態のことを言う。
 

resume()

suspendメソッドによって中断されているスレッドの動作を再開するメソッドである。suspendメソッドを使用しなければ、resumeメソッドを使用することもない。
 

スレッドを停止させるには

stopメソッドは推奨されないメソッドである。それでは、スレッドを停止させるにはどうすれよいのだろうか。
スレッドが自分自身を終了させるには、実行中のrunメソッドを終了するようにしておきます。startメソッドによって新しく起動されたスレッドは、runメソッドが終了すると消滅します。
 
自分以外のスレッドを終了させるには、一般的に次のような方法を用いる。
 

  1. runメソッドを実装したクラスで、スレッドの実行状態を表現するboolean型変数を宣言する。
  2. runメソッド内で、繰り返しスレッドの実行状態を確認し、実行状態が「終了」を示す値になっていればrunメソッドが終了されるようにしておく。
  3. そのスレッド実行状態を変更するためのメソッドを宣言する。
  4. スレッド実行状態を「実行中」を示す値に設定し、新しいスレッドを起動する。
  5. 外部から、スレッド実行状態を変更するためのメソッドを呼び出す。

 

class someThread extends Thread{
  private boolean running = true;
   
  public void start(){
    new Thread(this).start();
   }
   
  public void run(){
    while(running){
      ...... //スレッドで実行したい処理
    }
  }
   
  public void stopRunning(){
    running = false;
  }
}

 
スレッドの実行状態は、runningというメンバ変数で表現している。runningの初期値はtrueとする。スレッドが起動したあと、runメソッド内のwhileループは、runnningがtrueである限り繰り返し実行される。
外部からスレッドを停止させるにはstopRunningメソッドを呼び出す。ここでrunningの値がfalseに変更されるので、runメソッド内のwhileループは、次回の条件チェックの際に繰り返し処理を終了する。ループを抜けることによってrunメソッドも終了し、スレッドは消滅する。
 
 
以上
 
 
参考
マルチスレッドプログラミング | TECHSCORE(テックスコア)

Javaでマルチスレッドプログラミングを覚えようと、こちらのサイトを読んで写経してみた。前回の記事では、マルチスレッドの概要と実行方法をまとめた。今回はマルチスレッドの排他制御について。
 
複数のスレッドが同じオブジェクトを同時に操作すると、プログラムが予想外の動作をすることがある。この記事では、複数のスレッドの動作を制御し、同じオブジェクトが同時に操作されないようにする方法を説明する。
 

複数のスレッドが並行して単一のインスタンスを操作することの問題点

2つのスレッドは、同じオブジェクトを並行して同時に扱うことができる。2つのスレッドがあるオブジェクトのフィールド変数を同時に書き込んだりすると、プログラムが時として意図しない動作をすることがある。
 

class Account {
    private int balance = 0;         // 預金残高
    public void deposit(int money){
        int total = balance + money;
        balance = total;
    }
}

 
上記の預金口座を表すクラスでは、同じインスタンスに対して2つのスレッドが同時にアクセスした場合、問題が生じる恐れがある。
例えば、以下のような順序で処理が行われた場合、プログラムが意図していない結果となる。
 
スレッドAとスレッドBが同じインスタンスのdeposit(1000)を呼び出した場合、

  1. スレッドAがint total = balance + money;を実行する
  2. スレッドBがint total = balance + money;を実行する
  3. スレッドAがbalance = total;を実行する
  4. スレッドBがbalance = total;を実行する

 
意図としては、balance変数が2000になっていることを期待しているが、実際にはbalance変数は1000になってしまっている。
 
※totalはメソッドで定義されたローカル変数なので、メソッドの呼び出しごとに個別に領域確保される。つまり、それぞれのスレッドで別々の領域を使用している(この辺の挙動はJVMの仕様を参照してほしい)。
 
※balanceはインスタンス変数なので、スレッド1とスレッド2で領域を共有している。
 

スレッド固有の作業コピー領域

上記の内容の続きだが、以下のように複数行に分けて書いていたプログラムを1行で書いてしまえば、処理の重複を避けることが出来そうに見える。

public void diposit(int money){
    balance += money;
}

 
しかし、これでも誤動作の可能性がある。それぞれのスレッドは内部的に固有の作業コピー領域を持っており、より高速にプログラムを実行するために、変数の値を一時的にスレッド固有のメモリにコピーして作業することが許されている。上記の例では、balanceの値を共有メモリからスレッド固有メモリにコピーし、そこで加算処理を実施してから共有メモリに書き戻す、という処理を行う。つまり、このように1行でプログラムを書いたとしても、先ほどのプログラムと同じようなことが内部的に実行されてしまう。
 
作業コピー領域で起きていることを図に表すと以下のようになる。
※図はこちらのサイトから引用させていただいた。
ThreadWorkingCopy
 

synchronizedブロック

上記で扱ってきた問題を解決するにはsynchronizedブロックを利用する。
クラスやインスタンスはそれぞれ「ロック」というものを持っている。ロックとは「鍵」のことである。スレッドは、synchronizedブロックの開始時にsynchronized文で指定したオブジェクトのロックを取得し、終了時にそのロックを開放する。あるスレッドがロックを取得している状態では、他のスレッドは同じロックを取得することができず、ロックが返却されるまで待たされることになる。
ただし、ロックが使用中であるかどうかを気にするのは、あくまでも、スレッドがsynchronizedブロックを実行しようとしているときだけである。あるスレッドがsynchronizedブロックでオブジェクトを独占しているつもりでも、synchronizedを利用していない箇所からのアクセスを防ぐことはできない。

public void deposit(int money) {
    synchronized(this){
        int total = balance + money;
        balance = total;
    }
}

  

synchronizedメソッド

メソッド全体をオブジェクトthisに対するsynchronizedブロックとしたい場合、synchronizedメソッドを利用することができる。synchronizedメソッドは、メソッドの処理全体を、thisに対するsynchronizedブロックで囲んだのと同じ意味を持つ。

synchronized public void deposit(int money) {
    int total = balance + money;
    balance = total;
}

 
上記のような単純な処理でsychronizedメソッドを使う分には問題ない。しかし、長いメソッドや処理に時間のかかるメソッドでは無闇にsynchronizedメソッドを使うべきではない。他のスレッドがロック解放待ちのために長時間待たされるおそれがあるので、排他制御は、誤動作が起きない必要最低限の狭い範囲に限定して使用するべきである。
 

static synchronizedメソッド

クラス(static)メソッドもsynchronizedメソッドにすることができる。クラスメソッドはそのクラスのインスタンスが存在していなくても呼び出すことができるが、スレッドはどのオブジェクトのロックを取得するのか。
 
あるクラスがプログラムにおいて利用可能であるとき、必ずそのクラスに対応するjava.lang.Classクラスのオブジェクトが存在している。スレッドは、static synchronizedメソッドを実行するとき、そのクラスに対応するClassオブジェクトのロックを取得する。
なお、クラスに対応するClassオブジェクトは、ClassクラスのクラスメソッドであるforNameメソッドを用いて取得することができる。
 
つまり、以下の2つのコードは同じ意味になる。

class SomeClass {
    synchronized static public void someMethod() {
        //...
    }
}

 

class SomeClass {
    static public void someMethod() {
        synchronized(Class.forName("SomeClass")) {
            //...
        }
    }
}

 

volatile変数

「スレッド固有の作業コピー領域」の説明のところにも書いたように、各スレッドは、共有する変数の内容をスレッド固有の作業領域にコピーして作業を行う。したがって、共有する変数を使用するためには、作業コピーへの読み込みや、共有メモリ(変数)への書き込みを行う必要がある。この読み書きのタイミングにはある程度の自由が許されており、連続して同じ変数にアクセスするときには途中で書き戻さない(作業コピーを変数に反映しない)ことや、プログラムで記述した順序とは違う順序で書き戻すことが許されている。
 
次のプログラムのincrementメソッドでは、まずaを加算してからbを加算するように記述している。しかし、あるスレッドでincrementメソッド実行中に別スレッドからprintメソッドを実行した場合、「a=0,b=1」と表示される可能性がある。

class someClass {
    private int a = 0, b = 0;
    public void increment() {
        a++;
        b++;
    }
    public void print(){
        System.out.println("a=" + a + ",b=" + b);
    }
}

 
そのような意図しない動作を防ぐためには、共有される変数をvolatileとして宣言する。volatile変数は、スレッドからアクセスがあるたびに、必ず共有メモリ上の変数の値とスレッドの作業コピー上の値とを一致させる。

volatile private int a = 0, b = 0;

 
無用のトラブルを防ぐには、複数のスレッドからアクセスされる可能性のあるメンバ変数はvolatileとして宣言しておくのがよい。ただし、synchronizedブロックでのロックの開放時には、必ず作業コピーの内容は共有メモリに書き戻されるので、synchronizedブロック内からしかアクセスされない変数はvolatile変数にする必要はない。
 
 
以上
 
 
参考
マルチスレッドプログラミング | TECHSCORE(テックスコア)