不変オブジェクトが満たす必要がある条件
(1) オブジェクトの作成後、その状態を維持することはできません。修正される必要があります
(2) オブジェクトのすべてのフィールドは最終型です
(3) オブジェクトは正しく作成されます (オブジェクトの作成中に、この参照はオーバーフローしません)
不変オブジェクトについては、JDK の String クラスを参照してください。
最後のキーワード: クラス、メソッド、変数
(1) 変更されたクラス: このクラスは継承できません、String クラス、ラッパー クラス基本型 (Integer、Long など) はすべて Final 型です。最終クラスのメンバー変数は必要に応じて最終型に設定できますが、最終クラスのすべてのメンバー メソッドは暗黙的に最終メソッドとして指定されます。
(2) 変更方法: ロック方法は継承クラスによって変更されないため、効率的です。注: クラスのプライベート メソッドは、暗黙的に最終メソッドとして指定されます。
(3) 変更される変数: 基本データ型変数 (初期化後の値は変更できません)、参照型変数 (初期化後の値は変更できません)
は、JDK で Collections クラスを提供します。このクラスは、次のように unmodifiable で始まる多くのメソッドを提供します。
Collections.unmodifiableXXX: Collection、List、Set , Map...
Collections.unmodifiableXXX メソッドの XXX には、Collection、List、Set、Map を指定できます...
このとき、Collection、List、Set、および自分で作成したマップ Collections.unmodifiableXXX メソッドでは、不変になります。このとき、Collection、List、Set、Map の要素を変更すると、java.lang.UnsupportedOperationException 例外がスローされます。
Google の Guava には、次のように Immutable で始まるクラスが多数あります。
ImmutableXXX、XXX は Collection、List、Set、Map です...
注: Google の Guava を使用するには、Maven に次の依存関係パッケージを追加する必要があります。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency>
(1) アドホック スレッド クロージャ: プログラム制御の実装、最悪の場合、無視
(2) スタック クロージャ: ローカル変数、同時実行の問題なし
(3) ThreadLocal スレッド クロージャ: 特に優れたクロージャ メソッド
1. StringBuilder -> StringBuffer
StringBuilder: スレッドが安全ではありません;
StringBuffer: スレッドが安全ではありません;
文字列の連結マルチスレッド操作の場合は、StringBuffer を使用して
特定のメソッドで文字列スプライシング オブジェクトを定義します。これは StringBuilder を使用して実装できます。ローカル変数がメソッド内で定義されて使用されるとき、スタックは閉じられるため、その変数を使用するのは 1 つのスレッドだけです。複数のスレッドによる変数の操作は含まれません。StringBuilder を使用するだけです。
2. SimpleDateFormat -> JodaTime
SimpleDateFormat: スレッド セーフではないため、そのオブジェクトのインスタンス化を特定の時刻フォーマット メソッドに入れてスレッド セーフを実現できます
JodaTime: スレッド セーフ
package io.binghe.concurrency.example.commonunsafe; import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j public class DateFormatExample { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); //请求总数 public static int clientTotal = 5000; //同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(){ try { simpleDateFormat.parse("20191024"); } catch (ParseException e) { log.error("parse exception", e); } } }これを次のコードに変更するだけです。
package io.binghe.concurrency.example.commonunsafe; import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j public class DateFormatExample2 { //请求总数 public static int clientTotal = 5000; //同时并发执行的线程数 public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(){ try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20191024"); } catch (ParseException e) { log.error("parse exception", e); } } }JodaTime の場合、Maven に次の依存関係パッケージを追加する必要があります:
<dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9</version> </dependency>サンプル コードは次のとおりです:
package io.binghe.concurrency.example.commonunsafe; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j public class DateFormatExample3 { //请求总数 public static int clientTotal = 5000; //同时并发执行的线程数 public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ final int count = i; executorService.execute(() -> { try{ semaphore.acquire(); update(count); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(int i){ log.info("{} - {}", i, DateTime.parse("20191024", dateTimeFormatter)); } }
3. ArrayList、HashSet 、HashMap およびその他の Collections コレクション クラス これは、スレッド非安全なクラスです。
4. まず確認してから実行します: if(condition(a)){handle(a);}
注: この書き方はスレッドセーフではありません。 ! ! ! !
この操作を 2 つのスレッドが同時に実行し、同時に if 条件を判定し、変数をスレッド間で共有し、両方のスレッドが if 条件を満たした場合に 2 つのスレッドが実行します。 (a) ステートメント、現時点では、handle(a) ステートメントはスレッドセーフではない可能性があります。 安全でない点は、2 つの操作において、前の実行プロセスがスレッドセーフであっても、後続のプロセスもスレッドセーフですが、前後の実行プロセス間のギャップがアトミックではないことです。そのため、スレッドの安全性を脅かす問題も発生します。 実際の処理では、if(condition(a)){handle(a);} クラスが発生した場合、 a がスレッドで共有されているかどうかを考慮し、スレッドで共有されている場合は実行する必要があります。メソッドにロックを追加するか、if(condition(a)){handle(a);} の前後の 2 つの操作 (if 判定とコード実行) がアトミックであることを確認します。 4. スレッド セーフ - 同期コンテナ1. ArrayList -> Vector、StackArrayList: スレッド セーフ; Vector: 同期操作スレッドアンセーフなコードの例は次のとおりです:public class VectorExample { private static Vector<Integer> vector = new Vector<>(); public static void main(String[] args) throws InterruptedException { while (true){ for(int i = 0; i < 10; i++){ vector.add(i); } Thread thread1 = new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < vector.size(); i++){ vector.remove(i); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < vector.size(); i++){ vector.get(i); } } }); thread1.start(); thread2.start(); } } }スタック: Vector から継承され、先入れ後出し。 2. HashMap -> HashTable(Key, Value not be null)HashMap: スレッドは安全ではありません; HashTable: スレッドは安全です、HashTable を使用する場合は注意してください。 Key も Value も null にすることはできません;3. Collections.synchronizedXXX(List, Set, Map)注: コレクションを走査するときは、コレクションを更新しないでください。コレクション内の要素を削除する必要がある場合は、コレクションを走査し、最初に削除する必要がある要素をマークし、コレクションの走査が完了した後に削除操作を実行できます。たとえば、次のサンプルコード:
public class VectorExample3 { //此方法抛出:java.util.ConcurrentModificationException private static void test1(Vector<Integer> v1){ for(Integer i : v1){ if(i == 3){ v1.remove(i); } } } //此方法抛出:java.util.ConcurrentModificationException private static void test2(Vector<Integer> v1){ Iterator<Integer> iterator = v1.iterator(); while (iterator.hasNext()){ Integer i = iterator.next(); if(i == 3){ v1.remove(i); } } } //正常 private static void test3(Vector<Integer> v1){ for(int i = 0; i < v1.size(); i++){ if(i == 3){ v1.remove(i); } } } public static void main(String[] args) throws InterruptedException { Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); vector.add(3); //test1(vector); //test2(vector); test3(vector); } }
J.U.C表示的是java.util.concurrent报名的缩写。
ArrayList:线程不安全;
CopyOnWriteArrayList:线程安全;
写操作时复制,当有新元素添加到CopyOnWriteArrayList数组时,先从原有的数组中拷贝一份出来,然后在新的数组中进行写操作,写完之后再将原来的数组指向到新的数组。整个操作都是在锁的保护下进行的。
CopyOnWriteArrayList缺点:
(1)每次写操作都需要复制一份,消耗内存,如果元素特别多,可能导致GC;
(2)不能用于实时读的场景,适合读多写少的场景;
CopyOnWriteArrayList设计思想:
(1)读写分离
(2)最终一致性
(3)使用时另外开辟空间,解决并发冲突
注意:CopyOnWriteArrayList读操作时,都是在原数组上进行的,不需要加锁,写操作时复制,当有新元素添加到CopyOnWriteArrayList数组时,先从原有的集合中拷贝一份出来,然后在新的数组中进行写操作,写完之后再将原来的数组指向到新的数组。整个操作都是在锁的保护下进行的。
CopyOnWriteArraySet:线程安全的,底层实现使用了CopyOnWriteArrayList。
ConcurrentSkipListSet:JDK6新增的类,支持排序。可以在构造时,自定义比较器,基于Map集合。在多线程环境下,ConcurrentSkipListSet中的contains()方法、add()、remove()、retain()等操作,都是线程安全的。但是,批量操作,比如:containsAll()、addAll()、removeAll()、retainAll()等操作,并不保证整体一定是原子操作,只能保证批量操作中的每次操作是原子性的,因为批量操作中是以循环的形式调用的单步操作,比如removeAll()操作下以循环的方式调用remove()操作。如下代码所示:
//ConcurrentSkipListSet类型中的removeAll()方法的源码 public boolean removeAll(Collection<?> c) { // Override AbstractSet version to avoid unnecessary call to size() boolean modified = false; for (Object e : c) if (remove(e)) modified = true; return modified; }
所以,在执行ConcurrentSkipListSet中的批量操作时,需要考虑加锁问题。
注意:ConcurrentSkipListSet类不允许使用空元素(null)。
ConcurrentHashMap:线程安全,不允许空值
ConcurrentSkipListMap:是TreeMap的线程安全版本,内部是使用SkipList跳表结构实现
(1)ConcurrentSkipListMap中的Key是有序的,ConcurrentHashMap中的Key是无序的;
(2)ConcurrentSkipListMap支持更高的并发,对数据的存取时间和线程数几乎无关,也就是说,在数据量一定的情况下,并发的线程数越多,ConcurrentSkipListMap越能体现出它的优势。
注意:在非对线程下尽量使用TreeMap,另外,对于并发数相对较低的并行程序,可以使用Collections.synchronizedSortedMap,将TreeMap进行包装;对于高并发程序,使用ConcurrentSkipListMap提供更高的并发度;在多线程高并发环境中,需要对Map的键值对进行排序,尽量使用ConcurrentSkipListMap。
以上がJava スレッド セーフティ戦略の分析例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。