スレッドセーフの問題の解決策は何ですか?

一緒に書く習慣をつけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して2日目です。クリックしてイベントの詳細をご覧ください

スレッドセーフとは、データの不整合やデータの汚染なしに、メソッドまたは特定のコードを複数のスレッドで正しく実行できることを意味します。このようなプログラムをスレッドセーフと呼びます。それ以外の場合は、スレッドセーフではありません。Javaでは、スレッドセーフの問題を解決する方法は3つあります。

  1. AtomicIntegerなどのスレッドセーフクラスを使用します。
  2. ロックキューの実行
    1. 同期ロックを使用します。
    2. ReentrantLockを使用してロックします。
  3. スレッドローカル変数ThreadLocalを使用します。

次に、それらの実装を1つずつ見ていきます。

スレッドセーフの問題のデモンストレーション

0に等しい変数番号を作成し、次にスレッド1を作成して100万++の操作を実行し、同時にスレッド2を作成して100万の操作を実行します。スレッド1とスレッド2の両方が実行された後、値を出力します。数値変数の。、出力された結果が0の場合、それはスレッドセーフであることを意味します。それ以外の場合、スレッドセーフではありません。サンプルコードは次のとおりです。

public class ThreadSafeTest {
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number++;
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                number--;
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}
复制代码

上記のプログラムの実行結果を次の図に示します。image.png上記の実行結果から、数値変数の最終結果が0ではないことがわかります。これは、スレッドセーフの問題である期待される正しい結果と一致しません。マルチスレッドで。

スレッドセーフの問題を修正する

1.アトミッククラスAtomicInteger

AtomicIntegerはスレッドセーフクラスです。次のコードに示すように、++操作と--操作をアトミック操作に変換するために使用できます。これにより、非スレッドセーフの問題を解決できます。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    // 创建 AtomicInteger
    private static AtomicInteger number = new AtomicInteger(0);
    // 循环次数
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // ++ 操作
                number.incrementAndGet();
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // -- 操作
                number.decrementAndGet();
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("最终结果:" + number.get());
    }
}
复制代码

上記のプログラムの実行結果を次の図に示します。image.png

2.ロックを使用したキューの実行

Javaには、同期同期ロックとReentrantLock再入可能ロックの2種類のロックがあります。

2.1同期ロック同期

Synchronizedは、JVMレベルで実装されたロックを自動的にロックおよび解放する同期ロックです。その実装コードは次のとおりです。

public class SynchronizedExample {
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // 加锁排队执行
                synchronized (SynchronizedExample.class) {
                    number++;
                }
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                // 加锁排队执行
                synchronized (SynchronizedExample.class) {
                    number--;
                }
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}
复制代码

上記のプログラムの実行結果を次の図に示します。image.png

2.2 ReentrantLock

ReentrantLock再入可能ロックでは、プログラマーが自分でロックをロックして解放する必要があります。その実装コードは次のとおりです。

import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用 ReentrantLock 解决非线程安全问题
 */
public class ReentrantLockExample {
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;
    // 创建 ReentrantLock
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // 手动加锁
                number++;       // ++ 操作
                lock.unlock();  // 手动释放锁
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < COUNT; i++) {
                lock.lock();    // 手动加锁
                number--;       // -- 操作
                lock.unlock();  // 手动释放锁
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("number 最终结果:" + number);
    }
}
复制代码

上記のプログラムの実行結果を次の図に示します。image.png

3.スレッドローカル変数ThreadLocal

ThreadLocalスレッドローカル変数を使用すると、スレッドセーフの問題も解決できます。スレッドごとに独自のプライベート変数が作成されます。異なるスレッドは異なる変数を操作するため、非スレッドセーフの問題はありません。実装コードは次のとおりです。

public class ThreadSafeExample {
    // 创建 ThreadLocal(设置每个线程中的初始值为 0)
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
    // 全局变量
    private static int number = 0;
    // 循环次数(100W)
    private static final int COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 线程1:执行 100W 次 ++ 操作
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < COUNT; i++) {
                    // ++ 操作
                    threadLocal.set(threadLocal.get() + 1);
                }
                // 将 ThreadLocal 中的值进行累加
                number += threadLocal.get();
            } finally {
                threadLocal.remove(); // 清除资源,防止内存溢出
            }
        });
        t1.start();

        // 线程2:执行 100W 次 -- 操作
        Thread t2 = new Thread(() -> {
            try {
                for (int i = 0; i < COUNT; i++) {
                    // -- 操作
                    threadLocal.set(threadLocal.get() - 1);
                }
                // 将 ThreadLocal 中的值进行累加
                number += threadLocal.get();
            } finally {
                threadLocal.remove(); // 清除资源,防止内存溢出
            }
        });
        t2.start();

        // 等待线程 1 和线程 2,执行完,打印 number 最终的结果
        t1.join();
        t2.join();
        System.out.println("最终结果:" + number);
    }
}
复制代码

上記のプログラムの実行結果を次の図に示します。image.png

要約する

Javaでは、スレッドセーフの問題を解決する3つの方法があります。1。AtomicIntegerクラスなどのスレッドセーフクラスを使用します。2。ロック同期またはReentrantLockを使用して実行をキューに入れます。3。スレッドローカル変数ThreadLocalを使用して処理します。

善悪を判断し、他人の意見に耳を傾け、利益と損失を数えるのは自分次第です。

記事のコレクション:gitee.com/mydb/interv…

おすすめ

転載: juejin.im/post/7083651437720240142