同期
日本語 | 同期 |
英語 | synchronization |
ふりがな | どうき |
フリガナ | ドウキ |
複数のスレッドでタイミングを取ること。
「ある一組の処理」をあるスレッドが処理するとき、その処理全体が終わるまで他のスレッドがその処理をせず待ち続けること。
たとえば「変数Aの取得」と「変数Aの書き換え」をスレッド1とスレッド2が行う場合。
スレッド1が「変数Aの取得」を行った後、「変数Aの書き換え」を行う前にスレッド2が「変数Aの取得」の取得を行うとスレッド1とスレッド2の「変数Aの取得」の結果は同じとなる。
この状態でスレッド1が「変数Aの書き換え」を行い、その後スレッド2が「変数Aの書き換え」を行うと、スレッド2の「変数Aの書き換え」はスレッド1の「変数Aの書き換え」を無視して上書きしてしまう事になってしまう。
この場合、スレッド2はスレッド1の「変数Aの取得」と「変数Aの書き換え」の両方が終了するまで待たなければならない。つまり、スレッド1とスレッド2が処理のタイミングをわざとずらすことが必要となる。これを「同期を取る」と言う。また、スレッド1の処理中にスレッド2の処理を行わせないことを「排他する」と言う。
マルチスレッドの場合、同期を取らないと値が不正になる場合が多い。
その場合、synchronizedメソッドやsynchronizedブロックで同期を取る。
「ある一組の処理」をあるスレッドが処理するとき、その処理全体が終わるまで他のスレッドがその処理をせず待ち続けること。
たとえば「変数Aの取得」と「変数Aの書き換え」をスレッド1とスレッド2が行う場合。
スレッド1が「変数Aの取得」を行った後、「変数Aの書き換え」を行う前にスレッド2が「変数Aの取得」の取得を行うとスレッド1とスレッド2の「変数Aの取得」の結果は同じとなる。
この状態でスレッド1が「変数Aの書き換え」を行い、その後スレッド2が「変数Aの書き換え」を行うと、スレッド2の「変数Aの書き換え」はスレッド1の「変数Aの書き換え」を無視して上書きしてしまう事になってしまう。
この場合、スレッド2はスレッド1の「変数Aの取得」と「変数Aの書き換え」の両方が終了するまで待たなければならない。つまり、スレッド1とスレッド2が処理のタイミングをわざとずらすことが必要となる。これを「同期を取る」と言う。また、スレッド1の処理中にスレッド2の処理を行わせないことを「排他する」と言う。
マルチスレッドの場合、同期を取らないと値が不正になる場合が多い。
その場合、synchronizedメソッドやsynchronizedブロックで同期を取る。
参考サイト
- (参考サイトはありません)
// Sample.java
public class Sample
{
public static void main( String[] args )
{
try
{
SynchronizeClass synchronizeClass = new SynchronizeClass();
// 別スレッドの方を呼び出します。
OtherThread thread = new OtherThread( synchronizeClass, "normalMethod" );
thread.start();
// 2秒待ちます。OtherThreadの方を先に
// 実行するためです。
Thread.sleep( 2 * 1000 );
// こちらでも。
System.out.println( "Sample開始" );
// 普通のメソッドを呼び出します。
synchronizeClass.normalMethod( "Sample" );
System.out.println( "Sample終了" );
// OtherThread開始
// normalMethod()メソッド開始 [0] from OtherThread
// Sample開始
// normalMethod()メソッド終了 [100] from OtherThread
// normalMethod()メソッド開始 [100] from Sample
// OtherThread終了
// normalMethod()メソッド終了 [200] from Sample
// Sample終了
// このように、普通にマルチスレッドで共通のフィールドに
// アクセスすると、値がうまくとれない場合があります。
// 「値を取得して書き換える処理」は、この
// 「値の取得」と「値の書き換え」を1つのセットとし、
// この処理中に他のスレッドが同じ処理をしないように
// する必要があります。
// つまり、この「値を取得して書き換える処理」を
// タイミングを取って行うようにする、それが
// 「同期を取る」ということです。
// というわけで、synchronizedメソッドで
// 同期を取ります。
synchronizeClass = new SynchronizeClass();
thread = new OtherThread( synchronizeClass, "synchronizedMethod" );
thread.start();
// 2秒待ちます。OtherThreadの方を先に
// 実行するためです。
Thread.sleep( 2 * 1000 );
// こちらでも。
System.out.println( "Sample開始" );
// 普通のメソッドを呼び出します。
synchronizeClass.synchronizedMethod( "Sample" );
System.out.println( "Sample終了" );
// OtherThread開始
// synchronizedMethod()メソッド開始 [0] from OtherThread
// Sample開始
// synchronizedMethod()メソッド終了 [100] from OtherThread
// synchronizedMethod()メソッド開始 [100] from Sample
// OtherThread終了
// synchronizedMethod()メソッド終了 [200] from Sample
// Sample終了
// このように、synchronizedメソッドは、同じthisの場合は
// 呼び出せるのが1メソッドに限られます。そのため、
// 「値を取得して書き換える処理」を1セットとし、この
// セットが完了しない限り他のスレッドがこの処理を開始できない
// ようになっています。
// これが「同期を取る」ということです。同期を取る、つまり
// 処理のタイミングをスレッド間で合わせるわけです。
// また、あるスレッドが他のスレッドの処理を止めるため
// 「排他制御」とも言います。
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* 別スレッドとして実行するためのクラス。
*/
class OtherThread extends Thread
{
/** SynchronizeClassクラス。 */
private SynchronizeClass synchronizeClass;
/** 呼び出すメソッド。 */
private String methodName = "";
/** コンストラクタ。 */
public OtherThread( SynchronizeClass synchronizeClass, String methodName )
{
this.synchronizeClass = synchronizeClass;
this.methodName = methodName;
}
/**
* Threadクラスのrun()メソッドを
* オーバーライドしたメソッド。このメソッドが
* 別スレッドとして呼び出されます。
*/
public void run()
{
System.out.println( "OtherThread開始" );
// メソッドを呼び出します。
if( "normalMethod".equals( methodName ) )
{
synchronizeClass.normalMethod( "OtherThread" );
}
else
{
synchronizeClass.synchronizedMethod( "OtherThread" );
}
System.out.println( "OtherThread終了" );
}
}
/**
* 同期処理テスト用クラス。
*/
class SynchronizeClass
{
/** フィールド。 */
private int data = 0;
/**
* 普通のメソッド。
*/
public synchronized void normalMethod( String name )
{
try
{
System.out.println( "normalMethod()メソッド開始 [" + data + "] from " + name );
// dataの中身を取得します。
int i = data;
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
// その値に100を加えてdataにセットします。
data = i + 100;
System.out.println( "normalMethod()メソッド終了 [" + data + "] from " + name );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
/**
* synchronizedメソッド。
*/
public synchronized void synchronizedMethod( String name )
{
try
{
System.out.println( "synchronizedMethod()メソッド開始 [" + data + "] from " + name );
// dataの中身を取得します。
int i = data;
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
// その値に100を加えてdataにセットします。
data = i + 100;
System.out.println( "synchronizedMethod()メソッド終了 [" + data + "] from " + name );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* データクラス。
*/
class DataClass
{
/**
* privateなフィールド。
*/
private int data;
/**
* getter。
*/
public int getData()
{
return data;
}
/**
* setter。
*/
public void setData( int value )
{
this.data = value;
}
}
public class Sample
{
public static void main( String[] args )
{
try
{
SynchronizeClass synchronizeClass = new SynchronizeClass();
// 別スレッドの方を呼び出します。
OtherThread thread = new OtherThread( synchronizeClass, "normalMethod" );
thread.start();
// 2秒待ちます。OtherThreadの方を先に
// 実行するためです。
Thread.sleep( 2 * 1000 );
// こちらでも。
System.out.println( "Sample開始" );
// 普通のメソッドを呼び出します。
synchronizeClass.normalMethod( "Sample" );
System.out.println( "Sample終了" );
// OtherThread開始
// normalMethod()メソッド開始 [0] from OtherThread
// Sample開始
// normalMethod()メソッド終了 [100] from OtherThread
// normalMethod()メソッド開始 [100] from Sample
// OtherThread終了
// normalMethod()メソッド終了 [200] from Sample
// Sample終了
// このように、普通にマルチスレッドで共通のフィールドに
// アクセスすると、値がうまくとれない場合があります。
// 「値を取得して書き換える処理」は、この
// 「値の取得」と「値の書き換え」を1つのセットとし、
// この処理中に他のスレッドが同じ処理をしないように
// する必要があります。
// つまり、この「値を取得して書き換える処理」を
// タイミングを取って行うようにする、それが
// 「同期を取る」ということです。
// というわけで、synchronizedメソッドで
// 同期を取ります。
synchronizeClass = new SynchronizeClass();
thread = new OtherThread( synchronizeClass, "synchronizedMethod" );
thread.start();
// 2秒待ちます。OtherThreadの方を先に
// 実行するためです。
Thread.sleep( 2 * 1000 );
// こちらでも。
System.out.println( "Sample開始" );
// 普通のメソッドを呼び出します。
synchronizeClass.synchronizedMethod( "Sample" );
System.out.println( "Sample終了" );
// OtherThread開始
// synchronizedMethod()メソッド開始 [0] from OtherThread
// Sample開始
// synchronizedMethod()メソッド終了 [100] from OtherThread
// synchronizedMethod()メソッド開始 [100] from Sample
// OtherThread終了
// synchronizedMethod()メソッド終了 [200] from Sample
// Sample終了
// このように、synchronizedメソッドは、同じthisの場合は
// 呼び出せるのが1メソッドに限られます。そのため、
// 「値を取得して書き換える処理」を1セットとし、この
// セットが完了しない限り他のスレッドがこの処理を開始できない
// ようになっています。
// これが「同期を取る」ということです。同期を取る、つまり
// 処理のタイミングをスレッド間で合わせるわけです。
// また、あるスレッドが他のスレッドの処理を止めるため
// 「排他制御」とも言います。
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* 別スレッドとして実行するためのクラス。
*/
class OtherThread extends Thread
{
/** SynchronizeClassクラス。 */
private SynchronizeClass synchronizeClass;
/** 呼び出すメソッド。 */
private String methodName = "";
/** コンストラクタ。 */
public OtherThread( SynchronizeClass synchronizeClass, String methodName )
{
this.synchronizeClass = synchronizeClass;
this.methodName = methodName;
}
/**
* Threadクラスのrun()メソッドを
* オーバーライドしたメソッド。このメソッドが
* 別スレッドとして呼び出されます。
*/
public void run()
{
System.out.println( "OtherThread開始" );
// メソッドを呼び出します。
if( "normalMethod".equals( methodName ) )
{
synchronizeClass.normalMethod( "OtherThread" );
}
else
{
synchronizeClass.synchronizedMethod( "OtherThread" );
}
System.out.println( "OtherThread終了" );
}
}
/**
* 同期処理テスト用クラス。
*/
class SynchronizeClass
{
/** フィールド。 */
private int data = 0;
/**
* 普通のメソッド。
*/
public synchronized void normalMethod( String name )
{
try
{
System.out.println( "normalMethod()メソッド開始 [" + data + "] from " + name );
// dataの中身を取得します。
int i = data;
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
// その値に100を加えてdataにセットします。
data = i + 100;
System.out.println( "normalMethod()メソッド終了 [" + data + "] from " + name );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
/**
* synchronizedメソッド。
*/
public synchronized void synchronizedMethod( String name )
{
try
{
System.out.println( "synchronizedMethod()メソッド開始 [" + data + "] from " + name );
// dataの中身を取得します。
int i = data;
// 5秒待ちます。
Thread.sleep( 5 * 1000 );
// その値に100を加えてdataにセットします。
data = i + 100;
System.out.println( "synchronizedMethod()メソッド終了 [" + data + "] from " + name );
}
catch( InterruptedException e )
{
// sleep()メソッドが途中で中断されると
// InterruptedException例外が投げられます。
// 滅多にないですが。
e.printStackTrace();
}
}
}
/**
* データクラス。
*/
class DataClass
{
/**
* privateなフィールド。
*/
private int data;
/**
* getter。
*/
public int getData()
{
return data;
}
/**
* setter。
*/
public void setData( int value )
{
this.data = value;
}
}
// Sample.java public class Sample { public static void main( String[] args ) { try { SynchronizeClass synchronizeClass = new SynchronizeClass(); // 別スレッドの方を呼び出します。 OtherThread thread = new OtherThread( synchronizeClass, "normalMethod" ); thread.start(); // 2秒待ちます。OtherThreadの方を先に // 実行するためです。 Thread.sleep( 2 * 1000 ); // こちらでも。 System.out.println( "Sample開始" ); // 普通のメソッドを呼び出します。 synchronizeClass.normalMethod( "Sample" ); System.out.println( "Sample終了" ); // OtherThread開始 // normalMethod()メソッド開始 [0] from OtherThread // Sample開始 // normalMethod()メソッド終了 [100] from OtherThread // normalMethod()メソッド開始 [100] from Sample // OtherThread終了 // normalMethod()メソッド終了 [200] from Sample // Sample終了 // このように、普通にマルチスレッドで共通のフィールドに // アクセスすると、値がうまくとれない場合があります。 // 「値を取得して書き換える処理」は、この // 「値の取得」と「値の書き換え」を1つのセットとし、 // この処理中に他のスレッドが同じ処理をしないように // する必要があります。 // つまり、この「値を取得して書き換える処理」を // タイミングを取って行うようにする、それが // 「同期を取る」ということです。 // というわけで、synchronizedメソッドで // 同期を取ります。 synchronizeClass = new SynchronizeClass(); thread = new OtherThread( synchronizeClass, "synchronizedMethod" ); thread.start(); // 2秒待ちます。OtherThreadの方を先に // 実行するためです。 Thread.sleep( 2 * 1000 ); // こちらでも。 System.out.println( "Sample開始" ); // 普通のメソッドを呼び出します。 synchronizeClass.synchronizedMethod( "Sample" ); System.out.println( "Sample終了" ); // OtherThread開始 // synchronizedMethod()メソッド開始 [0] from OtherThread // Sample開始 // synchronizedMethod()メソッド終了 [100] from OtherThread // synchronizedMethod()メソッド開始 [100] from Sample // OtherThread終了 // synchronizedMethod()メソッド終了 [200] from Sample // Sample終了 // このように、synchronizedメソッドは、同じthisの場合は // 呼び出せるのが1メソッドに限られます。そのため、 // 「値を取得して書き換える処理」を1セットとし、この // セットが完了しない限り他のスレッドがこの処理を開始できない // ようになっています。 // これが「同期を取る」ということです。同期を取る、つまり // 処理のタイミングをスレッド間で合わせるわけです。 // また、あるスレッドが他のスレッドの処理を止めるため // 「排他制御」とも言います。 } catch( InterruptedException e ) { // sleep()メソッドが途中で中断されると // InterruptedException例外が投げられます。 // 滅多にないですが。 e.printStackTrace(); } } } /** * 別スレッドとして実行するためのクラス。 */ class OtherThread extends Thread { /** SynchronizeClassクラス。 */ private SynchronizeClass synchronizeClass; /** 呼び出すメソッド。 */ private String methodName = ""; /** コンストラクタ。 */ public OtherThread( SynchronizeClass synchronizeClass, String methodName ) { this.synchronizeClass = synchronizeClass; this.methodName = methodName; } /** * Threadクラスのrun()メソッドを * オーバーライドしたメソッド。このメソッドが * 別スレッドとして呼び出されます。 */ public void run() { System.out.println( "OtherThread開始" ); // メソッドを呼び出します。 if( "normalMethod".equals( methodName ) ) { synchronizeClass.normalMethod( "OtherThread" ); } else { synchronizeClass.synchronizedMethod( "OtherThread" ); } System.out.println( "OtherThread終了" ); } } /** * 同期処理テスト用クラス。 */ class SynchronizeClass { /** フィールド。 */ private int data = 0; /** * 普通のメソッド。 */ public synchronized void normalMethod( String name ) { try { System.out.println( "normalMethod()メソッド開始 [" + data + "] from " + name ); // dataの中身を取得します。 int i = data; // 5秒待ちます。 Thread.sleep( 5 * 1000 ); // その値に100を加えてdataにセットします。 data = i + 100; System.out.println( "normalMethod()メソッド終了 [" + data + "] from " + name ); } catch( InterruptedException e ) { // sleep()メソッドが途中で中断されると // InterruptedException例外が投げられます。 // 滅多にないですが。 e.printStackTrace(); } } /** * synchronizedメソッド。 */ public synchronized void synchronizedMethod( String name ) { try { System.out.println( "synchronizedMethod()メソッド開始 [" + data + "] from " + name ); // dataの中身を取得します。 int i = data; // 5秒待ちます。 Thread.sleep( 5 * 1000 ); // その値に100を加えてdataにセットします。 data = i + 100; System.out.println( "synchronizedMethod()メソッド終了 [" + data + "] from " + name ); } catch( InterruptedException e ) { // sleep()メソッドが途中で中断されると // InterruptedException例外が投げられます。 // 滅多にないですが。 e.printStackTrace(); } } } /** * データクラス。 */ class DataClass { /** * privateなフィールド。 */ private int data; /** * getter。 */ public int getData() { return data; } /** * setter。 */ public void setData( int value ) { this.data = value; } }