ホームページ  >  記事  >  Java  >  Java の synchronized キーワードについての深い理解

Java の synchronized キーワードについての深い理解

高洛峰
高洛峰オリジナル
2016-12-13 11:15:321109ブラウズ

synchronized キーワードは、このメソッドのロックを表します。これは、毎回どのスレッド A がこのメソッドを実行するかに関係なく、このメソッドを使用している他のスレッド B (または C D など) があるかどうかを確認する必要があることを意味します。そうでない場合は、このメソッドを使用しているスレッド B (または C D) がこのメソッドを実行します。そうでない場合は、同期メソッドと同期ブロックの 2 つの使用法を含めて、このメソッドを直接実行します。

1. 同期メソッド: メソッド宣言に synchronized キーワードを追加して、同期メソッドを宣言します。例:

public synchronized void accessVal(int newVal); 

同期されたメソッドは、クラス メンバー変数へのアクセスを制御します。各クラス インスタンスは、ロックに対応します。それ以外の場合、スレッドは、メソッドを呼び出すクラス インスタンスのロックを取得する必要があります。メソッドが実行されると、ロックは排他的に占有され、その後、ブロックされたスレッドはロックを取得して実行可能状態に戻ります。このメカニズムにより、各クラス インスタンスに対して、同期済みとして宣言されたすべてのメンバー関数のうち最大 1 つが実行可能状態になることが保証されます (クラス インスタンスに対応するロックを取得できるのは最大 1 つであるため)。クラス メンバーの変数へのアクセス違反 (クラス メンバー変数にアクセスできるすべてのメソッドが同期化されていると宣言されている場合)。 Java では、クラス インスタンスだけでなく、各クラスもロックに対応します。このように、クラスの静的メンバー関数を同期済みとして宣言して、クラスの静的メンバー変数へのアクセスを制御することもできます。同期メソッドの欠点: 大きなメソッドが同期済みとして宣言されている場合、通常、スレッド クラスのメソッド run() が同期済みとして宣言されている場合、スレッドのライフ サイクル全体にわたって実行され続けます。 . このクラスの同期メソッドへの呼び出しは失敗します。もちろん、クラス メンバー変数にアクセスするコードを特別なメソッドに組み込み、それを synchronized として宣言し、main メソッドで呼び出すことでこの問題を解決できますが、Java はより良い解決策、つまり synchronized ブロックを提供します。

2. 同期ブロック:

synchronized キーワードを使用して同期ブロックを宣言します。構文は次のとおりです。

synchronized(syncObject)
{  
//允许访问控制的代码  
} 

synchronized ブロックは、コードが実行される前にオブジェクト syncObject (前述したように、クラス インスタンスまたはクラス) のロックを取得する必要があるコード ブロックです。は上記と同じです。任意のコードブロックを対象とし、ロック対象を任意に指定できるため、柔軟性が高い。

同期 (this) についてのいくつかの理解

1. 2 つの同時スレッドが同じオブジェクト内の同期 (this) 同期コード ブロックにアクセスする場合、一度に 1 つのスレッドのみを実行できます。別のスレッドは、このコード ブロックを実行する前に、現在のスレッドがこのコード ブロックの実行を完了するまで待つ必要があります。

2. スレッドがオブジェクトの同期された (この) 同期されたコード ブロックにアクセスすると、オブジェクト内の他のすべての同期された (この) 同期されたコード ブロックへの他のスレッドのアクセスはブロックされます。

3. ただし、スレッドがオブジェクトの synchronized(this) 同期コード ブロックにアクセスする場合、別のスレッドは引き続き synchronized(this) 同期コード ブロック以外のオブジェクトの部分にアクセスできます。

4. 3 番目の例は、他の同期コード ブロックにも適用できます。つまり、スレッドがオブジェクトの synchronized (this) 同期コード ブロックにアクセスすると、このオブジェクトのオブジェクト ロックを取得します。その結果、オブジェクト オブジェクトのすべての同期されたコード部分への他のスレッドのアクセスが一時的にブロックされます。

5. 上記のルールは他のオブジェクトのロックにも適用されます。

synchronizedの簡単な例

public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
}
}
}

上記の例では、エラーの原因となる時間差を作り出すために、Javaのマルチスレッドサポートと同期メカニズムが深く利用されています。 synchronized キーワードを使用すると、マルチスレッドの共有データ同期の問題を簡単に解決できるようです。どうやって? ——結論を出す前に、同期キーワードの役割を深く理解する必要があります。

一般に、synchronized キーワードは、関数の修飾子として、または関数内のステートメントとして使用できます。これは、通常、同期メソッドおよび同期ステートメント ブロックと呼ばれるものです。さらに分類すると、synchronized はインスタンス変数、オブジェクト参照、静的関数、クラス リテラル (クラス名リテラル) に作用します。さらに詳しく説明する前に、いくつかの点を明確にする必要があります:

A. synchronized キーワードがメソッドに追加されるかオブジェクトに追加されるかに関係なく、取得されるロックはコードの一部や関数をロックとして扱うのではなく、オブジェクトです。また、synchronized メソッドは他のオブジェクトからアクセスされる可能性があります。スレッド。

B.各オブジェクトにはロックが 1 つだけ関連付けられています。

C.同期を実現するには多大なシステム オーバーヘッドが必要となり、デッドロックが発生する可能性もあるため、不必要な同期制御は避けるようにしてください。

次に、異なる場所で使用した場合のコードへの synchronized の影響について説明します。 P1 と P2 が同じクラスの異なるオブジェクトであると仮定します。このクラスは、次の状況で同期ブロックまたは同期メソッドを定義します。それらに電話してください。

1.関数修飾子として synchronized を使用する場合のサンプルコードは次のとおりです:

Public synchronized void methodAAA()
{
//…
}

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:

public void methodAAA()
{
synchronized (this)      // (1)
{
       //…..
}
}

(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。


2.同步块,示例代码如下:

public void method3(SomeObject so)
{
    synchronized(so)
    {
       //…..
    }
}

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable
{
        private byte[] lock = new byte[0]; // 特殊的instance变量
        Public void methodA()
        {
           synchronized(lock) { //… }
        }
        //…..
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

Class Foo
{
    public synchronized static void methodAAA()   // 同步的static 函数
    {
        //….
    }
    public void methodBBB()
    {
       synchronized(Foo.class)   // class literal(类名称字面常量)
    }
}

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。 还有一些技巧可以让我们对共享资源的同步访问更加安全:1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了  还有,比较常用的就有:Collections.synchronizedMap(new HashMap()),当然这个MAP就是生命在类中的全局变量,就是一个线程安全的HashMap,web的application是全web容器公用的,所以要使用线程安全来保证数据的正确。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。