Javaのさまざまなロックのメカニズムは何ですか

王林
リリース: 2023-04-20 08:34:12
転載
1101 人が閲覧しました

はじめに

一般的な Java ロックの概要

さまざまなロック メカニズムの区別とその使用方法

#ブロックしない場合は、スピン ロックを使用できます##1スレッドと複数のプロセスが同じロックを取得します##複数のスレッドが共通のロックを共有します##読み取り/書き込みロック (書き込み用の共有ロック) 公平なロックと不公平なロック
使用方法 ロック名
スレッドが同期リソースをロックしたいかどうかを確認します 楽観的ロックと悲観的ロック
同期リソースをロックした後、ブロックしますか?
再入可能ロック
複数のスレッドがキューに入るために競合する必要がありますか?

1. 楽観的ロックと悲観的ロック

悲観的ロック: 複数人で同時に実行することはできません。実行する場合は最初にロックしてください。このようなロック メカニズムの多くは、行ロック、テーブル ロック、読み取りロック、書き込みロックなど、操作前にすべてロックされる従来のリレーショナル データベースで使用されています。一貫性があるかどうかは、データにバージョンを追加し、データを同期的に更新し、バージョン番号を追加することです。ロックされず、バージョン番号を確認でき、人生におけるチケット取得と同様に複数人で操作できます。データを取得しに行くたびに他人が変更しないだろうと思うのでロックはかかりませんが、更新する際にはこの期間内に他人が更新したかどうかを判断し、バージョンなどの仕組みを利用することができます。数字。オプティミスティック ロックはマルチ読み取りアプリケーション タイプに適しており、スループットを向上させることができます。 Redis は、このチェック アンド セット メカニズムを使用してトランザクションを実装します。

(オプティミスティック ロックは、バージョン番号メカニズムと CAS アルゴリズムを使用して実装できます)

Javaのさまざまなロックのメカニズムは何ですか特定のCase は悲観的ロックと楽観的ロックを示しています

redis フレームワークで

multi を実行する前にコマンド watch

を実行します。具体的な形式は次のとおりです

watch key1 [key2]
ログイン後にコピー

具体的なコード形式は次のとおりです。

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set add 100
OK
127.0.0.1:6379> watch add
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby add 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 120
127.0.0.1:6379>
ログイン後にコピー

flushdb はデータベースをクリアします

Javaのさまざまなロックのメカニズムは何ですかただし、別のサーバーで exec を入力すると、エラーが表示されます

楽観的ロックのため、修正するとバージョンが変わります

一般:

悲観的ロック: 各人が単独で作業を完了すると、ロックとロック解除が実行されます。同時実行性の問題を解決するために、同時操作はサポートされていません。一度に 1 つずつしか操作できないため、非効率です。

オプティミスティック ロック: 何かが実行されるたびに、データのバージョン番号が比較されます。最初に提出した人が最初にバージョン番号を提出します

2. 公平なロックと不公平なロック

公平なロック: 早い者勝ち

不公平なロック: 順番が違います、あなたキューに飛び込む可能性があります

    公平なロック: 効率が比較的低い
  • 不公平なロック: 効率は高いですが、スレッドが枯渇する傾向があります
  • この関数 lock = new ReentrantLock(true); を通じてロックします。再入可能なロックを作成します。true は公正なロックを意味し、false は不公平なロックを意味します。デフォルトの不公平なロック

ソース コードを参照してください

パラメーターを指定した ReentrantLock(true) は公正なロックです

ReentrantLock(false) は不公平なロックです

主にNonfairSync()とFairSync()を呼び出します

public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
ログイン後にコピー

不公平なロックと公平なロックのソースコードを指定します

公平なロックのソースコードを表示します

static final class FairSync extends Sync {
   private static final long serialVersionUID = -3000897897090466540L;

  /**
  * Acquires only if reentrant or queue is empty.
   */
  final boolean initialTryLock() {
   Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
   if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
     setExclusiveOwnerThread(current);
      return true;
    }
    } else if (getExclusiveOwnerThread() == current) {
      if (++c < 0) // overflow
          throw new Error("Maximum lock count exceeded");
         setState(c);
         return true;
       }
    return false;
}
ログイン後にコピー

コードによる詳細な操作例

#
//第一步  创建资源类,定义属性和和操作方法
class LTicket {
    //票数量
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock(true);
    //卖票方法
    public void sale() {
        //上锁
        lock.lock();
        try {
            //判断是否有票
            if(number > 0) {
                System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);
            }
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

public class LSaleTicket {
    //第二步 创建多个线程,调用资源类的操作方法
    //创建三个线程
    public static void main(String[] args) {

        LTicket ticket = new LTicket();

new Thread(()-> {
    for (int i = 0; i < 40; i++) {
        ticket.sale();
    }
},"AA").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"BB").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"CC").start();
    }
}
ログイン後にコピー

結果のスクリーンショットは次のとおりです

#スレッドは実行されますが、BC スレッドは実行されず、不当なロックが発生しますJavaのさまざまなロックのメカニズムは何ですか

その設定を具体的に変更するには、リエントラント ロックでパラメーター化されたコンストラクターを使用できます。

コードをプライベート最終 ReentrantLock lock = new ReentrantLock(true) に変更します;

コードのスクリーンショットは次のとおりです

#3. リエントラント ロックJavaのさまざまなロックのメカニズムは何ですか

リエントラント ロックは再帰的ロックとも呼ばれます

リエントラント ロックでは、最初の亀裂が発生します。内部構造にずっと入ることができます

Object o = new Object();
new Thread(()->{
    synchronized(o) {
        System.out.println(Thread.currentThread().getName()+" 外层");

        synchronized (o) {
            System.out.println(Thread.currentThread().getName()+" 中层");

            synchronized (o) {
                System.out.println(Thread.currentThread().getName()+" 内层");
            }
        }
    }

},"t1").start();
ログイン後にコピー

synchronized (o) は、現在の { }

上記はすべて同期ロック メカニズムです

以下で説明しますロック ロック機構

public class SyncLockDemo {

    public synchronized void add() {
        add();
    }

    public static void main(String[] args) {
        //Lock演示可重入锁
        Lock lock = new ReentrantLock();
        //创建线程
        new Thread(()->{
            try {
                //上锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" 外层");

                try {
                    //上锁
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+" 内层");
                }finally {
                    //释放锁
                    lock.unlock();
                }
            }finally {
                //释放做
                lock.unlock();
            }
        },"t1").start();

        //创建新线程
        new Thread(()->{
            lock.lock();
            System.out.println("aaaa");
            lock.unlock();
        },"aa").start();
        }
 }
ログイン後にコピー

同じロック内のネストされたロックの場合、内部のネストされたロックはロックを解除されずに出力できますが、スレッドから飛び出して別のスレッドを実行すると、デッドロックが発生します

ロックとロック解除の概念を理解するには、

4. 読み取り/書き込みロック (共有ロックと排他ロック) Javaのさまざまなロックのメカニズムは何ですか

を記述する必要があります。読み取りロックは共有ロックであり、書き込みロックは排他的ロックです

共有ロックの特定の実装
  • 読み取り/書き込みロックはロックのグループを管理します1 つは読み取りロックのみで、もう 1 つは書き込みロックです。
  • 読み取り/書き込みロック: リソースは複数の読み取りスレッドまたは 1 つの書き込みスレッドによってアクセスできますが、読み取りスレッドと書き込みスレッドは同時に存在できません。読み取り/書き込み相互排他が行われます。読み取り-読み取り共有 (書き込みロックは排他的、読み取りロックは共有、書き込みロックの優先順位は読み取りロックよりも高い)
読み取り-書き込みロック ReentrantReadWriteLock

読み取りロックは ReentrantReadWriteLock.ReadLock, readLock( ) メソッド

書き込みロックは ReentrantReadWriteLock.WriteLock, writeLock() メソッド

読み取り/書き込みロック オブジェクトを作成する private ReadWriteLock rwLock = new ReentrantReadWriteLock();

書き込みロックrwLock.writeLock().lock( );、ロック解除は rwLock.writeLock().unlock();

読み取りロックとロック rwLock.readLock().lock();、ロック解除は rwLock.readLock( ).unlock();

ケース分析:

マルチスレッドをシミュレートしてマップ内のデータをフェッチおよび読み取ります

完全なコードは次のとおりです

//资源类
class MyCache {
    //创建map集合
    private volatile Map<String,Object> map = new HashMap<>();

    //创建读写锁对象
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    //放数据
    public void put(String key,Object value) {
        //添加写锁
        rwLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
            //暂停一会
            TimeUnit.MICROSECONDS.sleep(300);
            //放数据
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+" 写完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放写锁
            rwLock.writeLock().unlock();
        }
    }

    //取数据
    public Object get(String key) {
        //添加读锁
        rwLock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
            //暂停一会
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 取完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放读锁
            rwLock.readLock().unlock();
        }
        return result;
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        //创建线程放数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        TimeUnit.MICROSECONDS.sleep(300);

        //创建线程取数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}
ログイン後にコピー

5. ミューテックス ロック

相互排他ロックは排他ロックの従来の実装であり、特定のリソースには同時に 1 人の訪問者のみがアクセスできることを意味し、一意かつ排他的です

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//创建互斥锁并初始化

pthread_mutex_lock(&mutex);//对线程上锁,此时其他线程阻塞等待该线程释放锁

//要执行的代码段

pthread_mutex_unlock(&mutex);//执行完后释放锁
ログイン後にコピー

6. スピンロック

Baidu百科事典の説明を確認してください。詳細は次のとおりです:

它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名

通俗的来说就是一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务。

其特点:

  1. 持有锁时间等待过长,消耗CPU

  2. 无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题

  3. 自旋锁不会使线程状态发生切换,处于用户态(不会到内核态进行线程的状态转换),一直都是活跃,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。

其模拟算法如下

do{
	b=1;
	while(b){
		lock(bus);
		b = test_and_set(&lock);
		unlock(bus);
	}
	//临界区
	//lock = 0;
	//其余部分
}while(1)
ログイン後にコピー

7. 无锁 / 偏向锁 / 轻量级锁 / 重量级锁

  • 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功

  • 偏向锁:是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价

  • 轻量级锁:锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能

  • 重量级锁:线程并发加剧,线程的自旋超过了一定次数,或者一个线程持有锁,一个线程在自旋,还有线程要访问

以上がJavaのさまざまなロックのメカニズムは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート