私たちは日々の開発作業において、多かれ少なかれマルチスレッド プログラミングや並行性の問題にさらされていますが、オペレーティング システムやシステム ハードウェアのアップグレードに伴い、開発では並行プログラミングがますます使用されています。マルチスレッドはシステム リソースをより有効に活用することを目的としていますが、マルチスレッドを使用すると、いくつかの問題も発生します。最初にコードを見てみましょう。
private static int i = 0; private static void increse(){ i++; } public static void main(String[] args) { Thread[] threads = new Thread[20]; for (int i = 0; i < threads.length; i++){ threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++){ increse(); } } }); threads[i].start(); } while (Thread.activeCount() > 1){ Thread.yield(); } System.out.println(i); }
まず、このコードを見る限りは問題ありませんが、マルチスレッド環境の場合、このコードを実行した結果は基本的に異なります。 ここでは、20個のスレッドが起動され、その後、各スレッドが実行されます。 increse()
メソッドは変数 i
に対して代入演算を実行します。期待される出力は 200000
であるはずですが、出力が毎回異なるのはなぜですか。毛織物?理由はこの場所 i++
にあります。これが同時実行の問題の根本的な原因です。では、なぜ一見単純な i++
にこのような問題が発生するのでしょうか。ここでは、単純に Java メモリ モデル
(JMM) から学びます。 まず、JMM
は、実行時に各スレッドが作業メモリを持つことを規定し、次に変数 i を規定します。
はメイン メモリに保存されます。スレッドはデータを計算するたびに、現在の変数の値を取得するためにメイン メモリにアクセスする必要があります。簡単に言うと、スレッドは変数 i</ を変更します。 code> 計算結果が得られた後、データはメインメモリに更新されていません。つまり、このスレッドで取得されたデータが最新であるかどうかはわかりません。 。ただし、これは <code>JMM
の観点から見た簡単な説明にすぎません。i++
のこの一見単純な操作には、実際には i
の値を取得する 3 つの操作が含まれています。値を増やし、i
をインクリメントしてから、値を i
に割り当てます。これらの操作中に、他のスレッドは多くのことを行うために多くの時間を費やしますが、この i++
操作を同期するだけで十分なのでしょうか?と疑問に思う人もいるかもしれません。はい、どうやって同期しますか? increse()
方法对变量i
进行一个赋值操作,预期的一个输出应该是200000
,但是为什么会每一次的输出都不太一样呢?原因就在于这个地方i++
,这里就是产生并发问题的根本原因,那看起来很简单的一个i++
为什么会有这种问题?这里就简单的从java内存模型
(JMM)来了解一下,首先JMM
规定,每一个线程运行时都会有一个工作内存,然后变量i
是存储在主内存的,每次线程在计算数据的时候都要去主内存中获取当前变量的值,那么简单的来说,就是一个线程将变量i
计算得到结果后,还没有将这个数据刷新到主内存,在这个时候,其他的线程已经获取到了原来的值,换句话说,本线程中获取到的数据是否是最新的,这个是不知道的。但是这只是从JMM
角度来简单的说一下,i++
这个看似简单的操作其实包含了三个操作,获取i
的值,对i
进行自增,然后对i
进行赋值。就是在这几个操作中,其他的线程会有很多的时间来做很多的事情,那有人会问,是不是将这个i++
操作同步就可以了?是的,那该如何同步呢?
有人说用volatile
来修饰一下变量i
不就可以了么?是的,java
中volatile
这个关键字确实是提供了一个同步的功能,但是为什么在这里修改一下还是没有效果呢?原因就在于如果一个变量用volatile修饰之后,只是会让其他的线程立即能够知道当前变量的值是多少,这里就叫做可见性
,但是还是解决不了i++
这几个操作的问题,那如何处理,又有人提出用synchronized
这个关键字,不得不承认,这个关键字确实很强大,是能够解决这个问题,那我们有没有考虑过这个为什么可以解决这个问题,是如何解决的,那还是简单的说一下,首先synchronized
是属于JVM级别的,有这个关键字的方法或者代码块,最后会被解释成monitorenter
和monitorexit
指令,这两个字节码都明确需要一个reference
类型的参数来指出要锁定或者解锁的对象,像这样:
public synchronized String f(){ //code } synchronized(object){ //code }
看到这里,我们应该能明白synchronized
为什么可以解决上面程序的问题,但是我们还应该要明确一个概念就是原子性
,换句话说,就是我们在处理一些多线程的问题的时候,应该保证一些共享数据的操作是原子性的,这样才能保证正确性,看到这里,相信你也有了一个大概的理解,那我们来总结一下,在处理多线程的问题的时候,哪些点是值得注意的,可见性
,原子性
,有序性
i
を変更するには volatile
を使用すれば十分だという人もいます。はい、java
のキーワード volatile
は同期機能を提供しますが、ここで変更しても効果がないのはなぜですか?その理由は、変数が volatile で変更された場合、他のスレッドは現在の変数の値をすぐに知ることしかできないためです。これは visibility
と呼ばれますが、それでも問題 を解決することはできません。 i++</ コードのこれらの操作の問題にどう対処するか? また、キーワード <code>synchronized
を使用することを提案した人もいます。このキーワードは確かに非常に強力であり、この問題を解決できると認めざるを得ません。この問題が解決できる理由とその解決方法について考えたことはありますか。まず、synchronized
は JVM レベルのメソッドまたはコード ブロックに属します。キーワードについては、最後に monitorenter
命令と monitorexit
命令で説明します。どちらのバイトコードも、オブジェクトを示すために型 reference
のパラメータを明示的に必要とします。 public String f(String s1, String s2, String s3){ return s1 + s2 +s3; }
synchronized
が上記のプログラムの問題を解決できるのかが理解できるはずですが、 という概念も明確にする必要があります。変更 言い換えると、マルチスレッドの問題に対処するときは、正確性を確保するために、共有データに対する一部の操作がアトミックであることを確認する必要があります。これを読んだ後は、一般的なことも理解できると思います。理解できたので要約しましょう。マルチスレッドの問題、<code>可視性
、アトミック性
、秩序性
を扱うときにどのような点に注目する価値があるかを見てみましょう。これらのいくつかの点は、マルチスレッドが正しく行われることを保証するための前提条件です。順序付けとは何かという点については、メモリ命令の再順序付けが含まれますが、これについては後で説明します。 ここで指摘すべきもう 1 つの質問は、マルチスレッドの問題に対処するときに、同期する必要があるのか、ロックする必要があるのかということです。これは、以前、インターネット上でジョークがありました。もちろん、これは単なる冗談ですが、シングルスレッドのプログラムにはこうした問題がないことがわかります。答えは「はい」です。単一のスレッドにはリソース競合の問題がないため、再度議論する必要はありません。 それでは、どのような場合に同期を使用する必要があり、どのような場合に使用しない必要があるのでしょうか?コードの一部を見てみましょうrrreee
これは文字列の結合の方法です。逆コンパイルして、JVM がどのように行うのか見てみましょう。
以上が学習記事: Java の同期についての詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。