#同期が必要な理由
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
3. 同期方法
同期キーワード変更方法
同期キーワード変更方法があります。 Java のすべてのオブジェクトには組み込みロックがあるため、このキーワードを使用してメソッドが変更されると、組み込みロックによってメソッド全体が保護されます。このメソッドを呼び出す前に、組み込みロックを取得する必要があります。取得しないとブロックされます。 コードの例:public synchronized void save(){}
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
再入ロックを使用したスレッド同期の実現
同期をサポートするために、新しい 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
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 サイトの他の関連記事を参照してください。