ロックにはいくつかの方法のJava実装

ロックと同期、マルチスレッド二つの質問を避けない学習、Javaはsynchronizedキーワードやコードブロックを同期させるための方法を提供し、また、のような多くの使いやすい並行処理ツール提供:LockSupport、CyclicBarrierを、たCountDownLatch、セマフォを...

あなたは今まで自分自身にそれを達成するためのロックを考えたことはありますか?

「ラッシュチケット」プログラムを通じて、彼らは、同期とロックのメソッドを実装し、その長所と短所を分析するには、いくつかの異なる方法で使用されました。

スピン

スレッドをロックすると、ロジックコードを実行しない、無限ループを失敗させることです。

/**
 * @author 潘
 * @Description 抢票-自旋锁
 */
public class Ticket {
    //加锁标记
    private AtomicBoolean isLock = new AtomicBoolean(false);
    //票库存
    private int ticketCount = 10;

    //抢票
    public void bye(){
        while (!lock()) {
            //加锁失败,自旋
        }
        String name = Thread.currentThread().getName();
        //加锁成功,执行业务逻辑
        System.out.println(name + ":加锁成功...");
        System.out.println(name + ":开始抢票...");
        //SleepUtil.sleep(1000);
        ticketCount--;
        System.out.println(name + ":抢到了,库存:" + ticketCount);
        System.out.println(name + ":释放锁.");
        unlock();
    }

    //加锁的过程必须是原子操作,否则会导致多个线程同时加锁成功。
    public boolean lock(){
        return isLock.compareAndSet(false, true);
    }

    //释放锁
    public void unlock() {
        isLock.set(false);
    }

    public static void main(String[] args) {
        Ticket lock = new Ticket();
        //开启10个线程去抢票
        for (int i = 0; i < 10; i++) {
            new Thread(() -> lock.bye()).start();
        }
    }
}

次のように出力されます。

Thread-0:加锁成功...
Thread-0:开始抢票...
Thread-0:抢到了,库存:9
Thread-0:释放锁.
Thread-3:加锁成功...
Thread-3:开始抢票...
Thread-3:抢到了,库存:8
Thread-3:释放锁.
Thread-4:加锁成功...
Thread-4:开始抢票...
Thread-4:抢到了,库存:7
Thread-4:释放锁.
......

ロック操作の過程は、それ以外の場合は、同時に成功をロックする複数のスレッドにつながる、アトミックでなければなりません。

スピンロックを達成するための最も簡単な方法ですが、欠点も明らかです。

  • CPUは、CPUリソースを無駄に、アイドリングスピン。
  • 間違って使用した場合、スレッドがロックを取得していない、高いCPU使用率を引き起こし、さらにはシステムがクラッシュします。

スピン利回り+

パフォーマンスの問題スピンロックを解決するために、最初はできるだけCPUのアイドルを防止し、CPUリソースへのロック・スレッドの主導権を取得しないようにしましょう。

CPUリソースへのロック・スレッドのイニシアチブを得られない、それは()Thread.yieldによって実装することができます。

次のようにBYE()を最適化することができます。

public void bye(){
    while (!lock()) {
        //获取不到锁,主动让出CPU资源
        Thread.yield();
    }
    String name = Thread.currentThread().getName();
    //加锁成功,执行业务逻辑
    System.out.println(name + ":加锁成功...");
    System.out.println(name + ":开始抢票...");
    //SleepUtil.sleep(1000);
    ticketCount--;
    System.out.println(name + ":抢到了,库存:" + ticketCount);
    System.out.println(name + ":释放锁.");
    unlock();
}

Thread.yield()CPUリソースの外に出して、しかし、競争するために継続されますが、それは次のスレッドにCPUのタイムスライスを割り当てていきます可能性があります。

あまりにも多くのスレッドが、頻繁に歩留まりをスケジュールすると、CPUのオーバーヘッドが増加する場合は、競争二つのスレッドに適用+スピンを生み出します。

スリープ+スピン

CPUリソースの最大収量弾力の使用に加えて、あなたはまた、CPUリソースを占有していない、ロック・スレッドの休眠を得られないだろうスリープを使用することができます。

次のようにBYE()を最適化することができます。

public void bye(){
    while (!lock()) {
       //获取不到锁的线程,暂时休眠1ms,释放CPU资源
        SleepUtil.sleep(1);
    }
    String name = Thread.currentThread().getName();
    //加锁成功,执行业务逻辑
    System.out.println(name + ":加锁成功...");
    System.out.println(name + ":开始抢票...");
    //SleepUtil.sleep(1000);
    ticketCount--;
    System.out.println(name + ":抢到了,库存:" + ticketCount);
    System.out.println(name + ":释放锁.");
    unlock();
}

使用睡眠は、CPUへの圧力を軽減することができますが、欠点も明らかです。

  • 睡眠時間は制御できません

目的は、パフォーマンスを向上させ、応答時間を短縮するためにマルチスレッドを使用することで、我々は、スレッドの実行時間の終わりを予測することはできない、睡眠時間が1ナノ秒は、クロックに対してでなければなりません、でも1ミリ秒、並行性の高いシナリオでは、制御可能ではありません。

性能試験

私は簡単なテストを実施し、結果は以下の通りです億票を、スナッチ:

  • スピン:21806msを消費します。
  • +スピン収量:時間は2543ms。
  • 睡眠+スピン:時間1593ms。

参考の試験結果。

公園+スピン

以前のいくつかと比較すると、それはより良い一回の実施で、完了するまでにLockSupportの助けを必要としています。

/**
 * @author 潘
 * @Description 抢票-park+自旋
 */
public class TicketPark {
    //加锁标记
    private AtomicBoolean isLock = new AtomicBoolean(false);
    //票库存
    private int ticketCount = 10;
    //等待线程队列
    private final Queue<Thread> WAIT_THREAD_QUEUE = new LinkedBlockingQueue<>();

    //抢票
    public void bye(){
        while (!lock()) {
            //获取不到锁的线程,添加到队列,并休眠
            lockWait();
        }
        String name = Thread.currentThread().getName();
        //加锁成功,执行业务逻辑
        System.out.println(name + ":加锁成功...");
        System.out.println(name + ":开始抢票...");
        ticketCount--;
        System.out.println(name + ":抢到了,库存:" + ticketCount);
        System.out.println(name + ":释放锁.");
        unlock();
    }

    //加锁的过程必须是原子操作,否则会导致多个线程同时加锁成功。
    public boolean lock(){
        return isLock.compareAndSet(false, true);
    }

    //释放锁
    public void unlock() {
        isLock.set(false);
        //唤醒队列中的第一个线程
        LockSupport.unpark(WAIT_THREAD_QUEUE.poll());
    }

    public void lockWait(){
        //将获取不到锁的线程添加到队列
        WAIT_THREAD_QUEUE.add(Thread.currentThread());
        //并休眠
        LockSupport.park();
    }
}

java.util.concurrentパッケージの下に多くのクラスは、スピン公園+同期、使用していない実装されてReentrantLockのの例外を!

そのようなAロックのJava実装は広く、興味のある学生はまた、自分の手のロックを書き込むことができ、いくつかの方法に分かれています。

公開された100元の記事 ウォン称賛23 ビュー90000 +

おすすめ

転載: blog.csdn.net/qq_32099833/article/details/103149102