ホームページ > Java > &#&はじめる > Javaを同期する方法

Javaを同期する方法

爱喝马黛茶的安东尼
リリース: 2019-11-12 17:59:20
オリジナル
3363 人が閲覧しました

Javaを同期する方法

#同期が必要な理由

Java では、マルチスレッドの同時実行制御が可能です。複数のスレッドが共有リソース変数を操作する場合、 (データの追加、削除、変更、クエリなど) はデータが不正確になり、データ同士が競合するため、スレッドが操作を完了する前に他のスレッドから呼び出されるのを避けるために同期ロックが追加され、スレッドが確実に実行されるようにします。変数の一意性と精度。

1. 例

たとえば、銀行口座が 2 つのスレッドで同時に操作されている場合、1 つは 100 元を受け取り、もう 1 つは 100 元を入金します。元々アカウントのブロックが0だったとして、出金スレッドと入金スレッドが同時に発生した場合はどうなるのでしょうか?出金に失敗し、アカウント残高は 100 です。引き出しは正常に完了し、アカウント残高は 0 になりました。しかし、どの残高がどれに対応するのでしょうか?明確に伝えるのは難しいため、マルチスレッド同期の問題が発生します。

2. 同期がない状況

たとえば、銀行口座が 2 つのスレッドで同時に操作されている場合、1 つは 100 元を受け取り、もう 1 つは 100 元を入金します。 . .元々アカウントのブロックが0だったとして、出金スレッドと入金スレッドが同時に発生した場合はどうなるのでしょうか?出金に失敗した場合の口座残高は100、出金に成功した場合の口座残高は0となります。しかし、どの残高がどれに対応するのでしょうか?明確に伝えるのは難しいため、マルチスレッド同期の問題が発生します。

public class Bank {
   private int count =0;//账户余额
   
   //存钱
   public  void addMoney(int money){
       count +=money;
       System.out.println(System.currentTimeMillis()+"存进:"+money);
   }
    
    //取钱
    public  void subMoney(int money){
        if(count-money < 0){
            System.out.println("余额不足");
            return;
        }
        count -=money;
        System.out.println(+System.currentTimeMillis()+"取出:"+money);
    }
    
    //查询
    public void lookMoney(){
        System.out.println("账户余额:"+count);
    }
}
ログイン後にコピー
package threadTest;
public class SyncThreadTest {
public static void main(String args[]){
final Bank bank=new Bank();
Thread tadd=new Thread(new Runnable() {
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            bank.addMoney(100);
            bank.lookMoney();
            System.out.println("\n");
            
        }
    }
});
Thread tsub = new Thread(new Runnable() {
    
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            bank.subMoney(100);
            bank.lookMoney();
            System.out.println("\n");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }    
        }
    }
});
tsub.start();
tadd.start();
}
}
ログイン後にコピー

実行結果:

1502542307917取出:100
账号余额:100
1502542308917存进:100
1502542308917取出:100
账号余额:0
账号余额:0
1502542309917存进:100
账号余额:0
1502542309917取出:100
账号余额:0
ログイン後にコピー

2 つのスレッドが非同期メソッドに同時にアクセスするため、この時点でスレッドの安全性以外の問題が発生します。これら 2 つのスレッドがビジネス オブジェクト内のインスタンスを操作する場合同時に、変数、スレッドの安全性以外の問題が発生する可能性があります。

解決策: public void run() の前に synchronized キーワードを追加するだけです。

3. 同期方法

同期キーワード変更方法

同期キーワード変更方法があります。 Java のすべてのオブジェクトには組み込みロックがあるため、このキーワードを使用してメソッドが変更されると、組み込みロックによってメソッド全体が保護されます。このメソッドを呼び出す前に、組み込みロックを取得する必要があります。取得しないとブロックされます。

コードの例:

public synchronized void save(){}
ログイン後にコピー

注: synchronized キーワードは静的メソッドも変更できます。この時点で静的メソッドが呼び出されると、クラス全体がロックされます。

public class Bank {
private int count =0;//账户余额
//存钱
public  synchronized void addMoney(int money){
count +=money;
System.out.println(System.currentTimeMillis()+"存进:"+money);
}
//取钱
public  synchronized void subMoney(int money){
if(count-money < 0){
    System.out.println("余额不足");
    return;
}
count -=money;
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
System.out.println("账户余额:"+count);
}
}
ログイン後にコピー

実行結果:

余额不足
账号余额:0
1502543814934存进:100
账号余额:100
1502543815934存进:100
账号余额:200
1502543815934取出:100
账号余额:100
ログイン後にコピー

スレッド同期はこの方法で実現されます

同期コード ブロック

つまり、次のように変更されます。ステートメントの同期されたキーワード ブロック。

このキーワードによって変更されたステートメント ブロックは、同期を実現するための組み込みロックとともに自動的に追加されます。

次のようなコード:

synchronized(object){ 
}
ログイン後にコピー

注: 同期は高度な処理です。したがって、同期されたコンテンツは最小限に抑える必要があります。

通常、メソッド全体を同期する必要はなく、同期されたコード ブロックを使用してキー コードを同期するだけです。

public class Bank {
private int count =0;//账户余额
//存钱
public  void addMoney(int money){
synchronized (this) {
    count +=money;
}
System.out.println(System.currentTimeMillis()+"存进:"+money);
}
//取钱
public   void subMoney(int money){
synchronized (this) {
    if(count-money < 0){
        System.out.println("余额不足");
        return;
    }
    count -=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
System.out.println("账户余额:"+count);
}
}
ログイン後にコピー

実行結果は以下の通りです:

余额不足
账户余额:0
余额不足
账户余额:100
1502544966411存进:100
账户余额:100
1502544967411存进:100
账户余额:100
1502544967411取出:100
账户余额:100
1502544968422取出:100
ログイン後にコピー

これによりスレッド同期も実現され、メソッド同期よりも動作効率が高くなります。同期は高コストな動作なので、同期は最小限に抑える必要があります。 。 コンテンツ。通常、メソッド全体を同期する必要はなく、同期されたコード ブロックを使用してキー コードを同期するだけです。

特別なドメイン変数 (volatile) を使用してスレッド同期を実現します

a. volatile キーワードは、メンバー変数にアクセスするためのロックフリーのメカニズムを提供します。

b. volatile を使用してメンバー変数を変更することは、フィールドが他のスレッドによって更新される可能性があることを仮想マシンに伝えることと同じです;

c. したがって、メンバー変数が使用されるたびに、代わりに再計算する必要があります。値;

d.volatile はアトミック操作を提供せず、最終型変数の変更に使用することもできません。

Bank.java コードは次のとおりです:

package com.thread.demo;
/**
* Created by HJS on 2017/8/12.
*/
public class Bank {
private volatile int count =0;//账户余额
//存钱
public  void addMoney(int money){
synchronized (this) {
    count +=money;
}
System.out.println(System.currentTimeMillis()+"存进:"+money);
}
//取钱
public   void subMoney(int money){
synchronized (this) {
    if(count-money < 0){
        System.out.println("余额不足");
        return;
    }
    count -=money;
}
System.out.println(+System.currentTimeMillis()+"取出:"+money);
}
//查询
public void lookMoney(){
System.out.println("账户余额:"+count);
}
}
ログイン後にコピー

実行結果:

余额不足
账户余额:0
余额不足
账户余额:100
1502546287474存进:100
账户余额:100
1502546288474存进:100
1502546288474取出:100
账户余额:100
ログイン後にコピー

この時点で、順序が再び乱れており、別の同期問題があることを示しています。 volatile はアトミック操作を保証できないため、volatile は synchronized を置き換えることができません。さらに、volatile はコンパイラによるコードの最適化を妨げるため、使用できない場合は適用しないでください。その原理は、スレッドが volatile で変更された変数にアクセスするたびに、キャッシュから変数を読み取るのではなくメモリから変数を読み取るため、各スレッドがアクセスする変数値は同じになるということです。これにより同期が確実に行われます。

再入ロックを使用したスレッド同期の実現

同期をサポートするために、新しい java.util.concurrent パッケージが JavaSE5.0 に追加されました。 ReentrantLock クラスは、Lock インターフェイスを実装する再入可能で相互排他的なロックであり、同期されたメソッドおよびブロックを使用する場合と同じ基本的な動作とセマンティクスを持ち、その機能を拡張します。

ReenreantLock クラスの一般的に使用されるメソッドは次のとおりです。

ReentrantLock(): ReentrantLock インスタンスを作成します。

lock(): ロックを取得します。

unlock( ): ロックの解放

注: ReentrantLock() にも公平なロックを作成できる構築メソッドがありますが、プログラムの実行効率を大幅に低下させる可能性があるため、お勧めできません。

Bank.java コードは次のように変更されます:

public class Bank {
private  int count = 0;// 账户余额
//需要声明这个锁
private Lock lock = new ReentrantLock();
// 存钱
public void addMoney(int money) {
lock.lock();//上锁
try{
    count += money;
    System.out.println(System.currentTimeMillis() + "存进:" + money);
}finally{
    lock.unlock();//解锁
}
}
// 取钱
public void subMoney(int money) {
lock.lock();
try{
    if (count - money < 0) {
        System.out.println("余额不足");
        return;
    }
    count -= money;
    System.out.println(+System.currentTimeMillis() + "取出:" + money);
}finally{
    lock.unlock();
}
}
// 查询
public void lookMoney() {
System.out.println("账户余额:" + count);
}
}
ログイン後にコピー

実行結果:

余额不足
账户余额:0
1502547439892存进:100
账户余额:100
1502547440892存进:100
账户余额:200
1502547440892取出:100
账户余额:100
ログイン後にコピー

注: Lock オブジェクトと同期キーワードの選択について:

a. どちらも使用せず、java.util.concurrent パッケージによって提供されるメカニズムを使用して、ユーザーがすべてのロック関連コードを処理できるようにすることが最善です。

b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码。

c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。

使用局部变量实现线程同步

代码如下:

public class Bank {
private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
    // TODO Auto-generated method stub
    return 0;
}
};
// 存钱
public void addMoney(int money) {
count.set(count.get()+money);
System.out.println(System.currentTimeMillis() + "存进:" + money);
}
// 取钱
public void subMoney(int money) {
if (count.get() - money < 0) {
    System.out.println("余额不足");
    return;
}
count.set(count.get()- money);
System.out.println(+System.currentTimeMillis() + "取出:" + money);
}
// 查询
public void lookMoney() {
System.out.println("账户余额:" + count.get());
}
}
ログイン後にコピー

运行结果如下:

复制代码
余额不足
账户余额:0
余额不足
1502547748383存进:100
账户余额:100
账户余额:0
余额不足
账户余额:0
1502547749383存进:100
账户余额:200
ログイン後にコピー

看了运行效果,一开始一头雾水,怎么只让存,不让取啊?看看ThreadLocal的原理:

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。

ThreadLocal 类的常用方法

ThreadLocal() : 创建一个线程本地变量     

get() : 返回此线程局部变量的当前线程副本中的值     

initialValue() : 返回此线程局部变量的当前线程的"初始值"     

set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

注:ThreadLocal与同步机制

a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。 

b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式。

php中文网,大量的免费Java入门教程,欢迎在线学习!

以上がJavaを同期する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート