ホームページ  >  記事  >  Java  >  Java における同期されたキーワードとスレッドの安全性の問題の分析例

Java における同期されたキーワードとスレッドの安全性の問題の分析例

高洛峰
高洛峰オリジナル
2017-01-05 14:47:481099ブラウズ

まず、synchronized の基本的な使用法を確認しましょう:

synchronized コード ブロック、変更されたコードは synchronized ステートメント ブロックになり、そのスコープはこのコード ブロックのオブジェクトを呼び出すことです。synchronized キーワードを使用すると、コードセグメント コードセグメントに同期を追加できる場合は、メソッド全体に同期を追加しないでください。これは、ロックの粒度を減らし、コードの同時実行性を高めると呼ばれます。

同期メソッドの場合、変更されたメソッドは同期メソッドになり、そのスコープはメソッド全体で、ターゲット オブジェクトはこのメソッドを呼び出すオブジェクトです。

同期された静的メソッドは静的メソッドを変更します。そのスコープは静的メソッド全体で、そのターゲットはこのクラスのすべてのオブジェクトです。

synchronized クラス。そのスコープは synchronized(className.class) の Synchronized の後の括弧で囲まれた部分であり、それが作用するオブジェクトはすべてこのクラスのオブジェクトです。

synchronized() () はロックされたオブジェクトです。 synchronized(this) はオブジェクト自体をロックするだけであり、同じクラスの別のオブジェクトによって呼び出される synchronized メソッドはロックされず、 synchronized(className.class) はグローバル ロックを使用して実装されます。さらに、特定のオブジェクトを () に追加して、特定のオブジェクトをロックすることもできます。

synchronized (object) {
 //在同步代码块中对对象进行操作 
}

synchronized キーワードとスレッド セーフ
synchronized キーワードを使用してコードをラップすると、スレッドの同期が安全になると考えました。テストしてみました。完全に間違っていることが分かりました。真にスレッドセーフにするためには、synchronized を正しく使用する必要があります。 。 。この書き方は分かっているのですが、怠惰で間違った書き方をしているのではないかと常々思っていました。
まだ基礎が出来ていないようです。まだまだ見直しと強化が必要です!仕事でこの種の間違いを犯すことは許されないことです。同期されたキーワードが使用される場所では、データは機密であることを知っておく必要があります。たくさん汗をかきます。 。 。
最初にコードを投稿してください:

package com; 
  
public class ThreadTest { 
 public static void main(String[] args) { 
  MyThread m1 = new MyThread(1); 
  MyThread m2 = new MyThread(2); 
  m1.start(); 
  m2.start(); 
 } 
} 
  
final class MyThread extends Thread { 
 private int val; 
  
 public MyThread(int v) { 
  val = v; 
 } 
 //这种做法其实是非线程安全的 
 public synchronized void print1(int v) { 
  for (int i = 0; i < 100; i++) { 
   System.out.print(v); 
  } 
 } 
  
 public void print2(int v) { 
  //线程安全 
  synchronized (MyThread.class) { 
   for (int i = 0; i < 100; i++) { 
    System.out.print(v); 
   } 
  } 
 } 
  
 public void run() { 
  print1(val); 
  // print2(val); 
 } 
}

怠けて汗を流すだけです。 。 。プログラマはいつも怠け者です。できるだけ少なく書きます。簡単に呼び出せるように、MyThread を匿名の最終内部クラスとして作成しました。最も直接的な継承 Thread を使用してスレッド クラスを実装し、実行する必要がある run() メソッドを定義します。
まず、print2() メソッドにアノテーションを付けて、print1() の結果が何であるかを確認します。 print1() は synchronized キーワードを使用して定義されたメソッドであり、これでもスレッド セーフを実現できると常々思っていました。誰もが知っているように、私は間違っていました。
main() メソッドを直接実行してみましょう。コンソールの出力結果は次のとおりです。

1212111121212121212121212121212121212121222222212121212。。。

は、1 と 2 の一連のクロスプリント結果です。私の main メソッドでは、最初に m1 を実行し、次に m2 を実行します。これは、スレッドの同期が達成されていないことを示しています。

MyThread m1 = new MyThread(1); 
MyThread m2 = new MyThread(2); 
m1.start(); 
m2.start();

次に run メソッドの print1() をコメントアウトして print2(); を実行します
コンソールは次のように出力します:

11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

このスレッドは確かに安全だといつも思っていましたが、これが原因です。コードの書き方 しばらく深く考えずにいたのですが、今日になって初めて間違いに気づきました。怠けないことが時には得になるようです。良い基礎を築くことが重要です。長年の間違いを正す。

具体的な理由を見てみましょう。

synchronized キーワードは、関数の修飾子として、または関数内のステートメントとして使用できます。これは、通常、synchronized メソッドおよび synchronized ステートメント ブロックと呼ばれます。さらに分類すると、synchronized はインスタンス変数、オブジェクト参照、静的関数、クラス リテラル (クラス名リテラル) に作用します。
さらに詳しく説明する前に、いくつかの点を明確にする必要があります。
A. synchronized キーワードがメソッドに追加されるかオブジェクトに追加されるかに関係なく、取得されるロックはコードの一部や関数をロックとして扱うのではなく、オブジェクトです。また、synchronized メソッドは他のオブジェクトからアクセスされる可能性があります。スレッド。
B.各オブジェクトにはロックが 1 つだけ関連付けられています。
C.同期を実現するには多大なシステム オーバーヘッドが必要となり、デッドロックが発生する可能性もあるため、不必要な同期制御は避けるようにしてください。
次に、異なる場所で使用した場合のコードへの synchronized の影響について説明します。
P1 と P2 が同じクラスの異なるオブジェクトであると仮定します。このクラスは、次の状況で P1 と P2 が使用できる同期ブロックまたは同期メソッドを定義します。 。 彼らへ電話します。
1. synchronized を関数修飾子として使用した場合のサンプルコードは次のとおりです:

Public synchronized void methodAAA() 
{ 
//…. 
}

これが同期メソッドです。では、このときどのオブジェクトが同期ロックされているでしょうか。ロックするのは、この同期メソッドを呼び出すオブジェクトです。つまり、オブジェクト P1 が異なるスレッドでこの同期メソッドを実行すると、それらの間で相互排他が形成され、同期効果が得られます。ただし、このオブジェクトが属するクラスが生成する別のオブジェクトP2は、任意にsynchronizedキーワードを指定してこのメ​​ソッドを呼び出すことができます。
上記のサンプル コードは、次のコードと同等です:

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

(1) は何を指しますか?これは、このメソッドを呼び出すオブジェクト (P1 など) を指します。同期メソッドの本質は、オブジェクト参照に synchronized を適用することであることがわかります。 ——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()――这样,调用端得到的就是对象副本的引用了。

总结一些synchronized注意事项:

当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。两个线程间是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。(两个线程使用的是同一个对象)

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞(同上,两个线程使用的是同一个对象)。

更多实例解析Java中的synchronized关键字与线程安全问题相关文章请关注PHP中文网!

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