Rumah  >  Artikel  >  Java  >  Kaedah pelaksanaan Java LockSupport tulisan tangan

Kaedah pelaksanaan Java LockSupport tulisan tangan

WBOY
WBOYke hadapan
2023-05-07 08:25:06548semak imbas

    Kata Pengantar

    Antara pelbagai alatan konkurensi yang disediakan kepada kami dalam JDK, seperti pelaksanaan dalaman ReentrantLock dan alatan lain, alat sering digunakan , alat ini ialah LockSupport. LockSupport memberikan kami fungsi yang sangat berkuasa Ia adalah primitif paling asas untuk menyekat benang.

    Prinsip pelaksanaan LockSupport

    Sebelum memahami prinsip pelaksanaan LockSupport, mari kita gunakan dahulu case untuk memahami fungsi LockSupport!

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
     
    public class Demo {
     
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("park 之前");
          LockSupport.park(); // park 函数可以将调用这个方法的线程挂起
          System.out.println("park 之后");
        });
        thread.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("主线程休息了 5s");
        System.out.println("主线程 unpark thread");
        LockSupport.unpark(thread); // 主线程将线程 thread 唤醒 唤醒之后线程 thread 才可以继续执行
      }
    }

    Keluaran kod di atas adalah seperti berikut:

    sebelum taman
    Benang utama berehat selama 5s
    Benang utama nyahletak benang
    selepas taman

    Pada pandangan pertama, fungsi yang dilaksanakan dengan meletakkan dan menyahparkan LockSupport dan fungsi yang dilaksanakan oleh menunggu dan isyarat nampaknya sama, tetapi sebenarnya mereka tidak.

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
     
    public class Demo02 {
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          try {
            TimeUnit.SECONDS.sleep(5);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println("park 之前");
          LockSupport.park(); // 线程 thread 后进行 park 操作 
          System.out.println("park 之后");
        });
        thread.start();
        System.out.println("主线程 unpark thread");
        LockSupport.unpark(thread); // 先进行 unpark 操作
     
      }
    }

    Keluaran kod di atas Keputusan adalah seperti berikut:

    Benang utama nyahletak benang
    Sebelum taman
    Selepas taman

    Di kod di atas, utas utama akan mula-mula melakukan operasi nyahletak, dan kemudian utas benang hanya menjalankan operasi taman, dalam kes ini program juga boleh dilaksanakan secara normal. Tetapi jika panggilan isyarat adalah sebelum panggilan tunggu, program tidak akan dilaksanakan Contohnya, kod berikut:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class Demo03 {
     
      private static final ReentrantLock lock = new ReentrantLock();
      private static final Condition condition = lock.newCondition();
     
      public static void thread() throws InterruptedException {
        lock.lock();
     
        try {
          TimeUnit.SECONDS.sleep(5);
          condition.await();
          System.out.println("等待完成");
        }finally {
          lock.unlock();
        }
      }
     
      public static void mainThread() {
        lock.lock();
        try {
          System.out.println("发送信号");
          condition.signal();
        }finally {
          lock.unlock();
          System.out.println("主线程解锁完成");
        }
      }
     
      public static void main(String[] args) {
        Thread thread = new Thread(() -> {
          try {
            thread();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        });
        thread.start();
     
        mainThread();
      }
    }

    Keluaran kod di atas adalah seperti berikut:

    Isyarat hantar

    Pembukaan utas utama selesai

    Dalam kod di atas, "menunggu selesai" tidak akan dicetak Ini kerana fungsi isyarat dipanggil sebelum menunggu, dan fungsi isyarat akan hanya bertindak balas kepadanya. Fungsi tunggu yang dilaksanakan sebelum ini mempunyai kesan, tetapi tidak akan menjejaskan tunggu yang dipanggil selepasnya.

    Apakah sebab kesan ini?

    Malah, apabila JVM melaksanakan LockSupport, ia akan mengekalkan pembolehubah pembilang _counter secara dalaman untuk setiap utas Pembolehubah ini mewakili "bilangan lesen Hanya apabila terdapat lesen, utas itu boleh dilaksanakan, tetapi bilangan maksimum lesen pada masa yang sama hanya boleh 1. Apabila taman dipanggil sekali, bilangan lesen akan dikurangkan sebanyak satu. Apabila unpark dipanggil sekali, kaunter akan dinaikkan satu, tetapi nilai kaunter tidak boleh melebihi 1.

    Apabila thread memanggil park, ia perlu menunggu untuk mendapatkan lesen Hanya selepas mendapat lesen, thread boleh terus melaksanakan, atau jika lesen telah diperoleh sebelum meletak, maka ia tidak akan Jika perlu. disekat, ia boleh dilaksanakan secara langsung.

    Laksanakan LockSupport anda sendiri

    Prinsip Pelaksanaan

    Dalam artikel sebelum ini, kami telah memperkenalkan prinsip locksupport, dan pelaksanaan utama dalamannya adalah melalui lesen:

    • Bilangan maksimum lesen yang boleh diperolehi oleh setiap urutan ialah 1.

    • Apabila kaedah unpark dipanggil, benang boleh mendapatkan lesen Had atas bilangan lesen ialah 1. Jika sudah ada lesen, lesen tidak boleh dikumpul.

    • Apabila memanggil kaedah taman, jika utas memanggil kaedah taman tidak mempunyai lesen, utas ini perlu digantung sehingga utas lain memanggil kaedah unpark untuk mengeluarkannya kepada utas ini . Lesen diperlukan sebelum urutan boleh terus dilaksanakan. Tetapi jika benang sudah mempunyai lesen, benang tidak akan disekat dan boleh dilaksanakan secara langsung.

    Melaksanakan peruntukan protokol LockSupport oleh kami sendiri

    Dalam pelaksanaan Parker kami sendiri, kami juga boleh memberi setiap urutan pembilang untuk merekodkan bilangan lesen untuk utas itu. Apabila lesen Apabila bilangan lesen lebih daripada atau sama dengan 0, utas boleh dilaksanakan, jika tidak, utas perlu disekat Peraturan khusus protokol adalah seperti berikut:

      Bilangan lesen untuk urutan awal ialah 0.
    • Jika nilai pembilang bersamaan dengan 1 dan nilai pembilang menjadi 0 apabila kita memanggil park, utas boleh terus dilaksanakan.
    • Jika nilai pembilang adalah sama dengan 0 apabila kita memanggil park, utas tidak boleh terus melaksanakan utas perlu digantung dan nilai pembilang ditetapkan kepada -1.
    • Jika nilai pembilang bagi utas yang tidak diletak adalah sama dengan 0 apabila kita memanggil nyahletak, kita perlu menukar nilai pembilang kepada 1.
    • Jika nilai pembilang benang yang tidak diletak adalah sama dengan 1 apabila kita memanggil nyahpark, tidak perlu menukar nilai pembilang, kerana nilai maksimum pembilang ialah 1.
    • Apabila kita memanggil unpark, jika nilai pembilang sama dengan -1, ini bermakna benang telah digantung, dan benang perlu dibangkitkan dan nilai pembilang perlu ditetapkan kepada 0. .
    • Alat

    Oleh kerana ia melibatkan menyekat dan membangkitkan benang, kita boleh menggunakan kunci reentrant ReentrantLock dan pembolehubah keadaan Condition, jadi kita perlu biasa dengan penggunaan ini dua alatan.

    ReentrantLock digunakan terutamanya untuk mengunci dan membuka kunci, dan digunakan untuk melindungi kawasan kritikal.

    Kaedah Condition.awat digunakan untuk menyekat benang.

    kaedah Condition.signal digunakan untuk membangkitkan benang.

    因为我们在unpark方法当中需要传入具体的线程,将这个线程发放许可证,同时唤醒这个线程,因为是需要针对特定的线程进行唤醒,而condition唤醒的线程是不确定的,因此我们需要为每一个线程维护一个计数器和条件变量,这样每个条件变量只与一个线程相关,唤醒的肯定就是一个特定的线程。我们可以使用HashMap进行实现,键为线程,值为计数器或者条件变量。

    具体实现

    因此综合上面的分析我们的类变量如下:

    private final ReentrantLock lock; // 用于保护临界去
    private final HashMap<Thread, Integer> permits; // 许可证的数量
    private final HashMap<Thread, Condition> conditions; // 用于唤醒和阻塞线程的条件变量

    构造函数主要对变量进行赋值:

    public Parker() {
      lock = new ReentrantLock();
      permits = new HashMap<>();
      conditions = new HashMap<>();
    }

    park方法

    public void park() {
      Thread t = Thread.currentThread(); // 首先得到当前正在执行的线程
      if (conditions.get(t) == null) { // 如果还没有线程对应的condition的话就进行创建
        conditions.put(t, lock.newCondition());
      }
      lock.lock();
      try {
        // 如果许可证变量还没有创建 或者许可证等于0 说明没有许可证了 线程需要被挂起
        if (permits.get(t) == null || permits.get(t) == 0) {
          permits.put(t, -1); // 同时许可证的数目应该设置为-1
          conditions.get(t).await();
        }else if (permits.get(t) > 0) {
          permits.put(t, 0); // 如果许可证的数目大于0 也就是为1 说明线程已经有了许可证因此可以直接被放行 但是需要消耗一个许可证
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      } finally {
        lock.unlock();
      }
    }

    unpark方法

    public void unpark(Thread thread) {
      Thread t = thread; // 给线程 thread 发放一个许可证
      lock.lock();
      try {
        if (permits.get(t) == null) // 如果还没有创建许可证变量 说明线程当前的许可证数量等于初始数量也就是0 因此方法许可证之后 许可证的数量为 1
          permits.put(t, 1);
        else if (permits.get(t) == -1) { // 如果许可证数量为-1,则说明肯定线程 thread 调用了park方法,而且线程 thread已经被挂起了 因此在 unpark 函数当中不急需要将许可证数量这是为0 同时还需要将线程唤醒
          permits.put(t, 0);
          conditions.get(t).signal();
        }else if (permits.get(t) == 0) { // 如果许可证数量为0 说明线程正在执行 因此许可证数量加一
          permits.put(t, 1);
        } // 除此之外就是许可证为1的情况了 在这种情况下是不需要进行操作的 因为许可证最大的数量就是1
      }finally {
        lock.unlock();
      }
    }

    完整代码

    import java.util.HashMap;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class Parker {
     
      private final ReentrantLock lock;
      private final HashMap<Thread, Integer> permits;
      private final HashMap<Thread, Condition> conditions;
     
      public Parker() {
        lock = new ReentrantLock();
        permits = new HashMap<>();
        conditions = new HashMap<>();
      }
     
      public void park() {
        Thread t = Thread.currentThread();
        if (conditions.get(t) == null) {
          conditions.put(t, lock.newCondition());
        }
        lock.lock();
        try {
          if (permits.get(t) == null || permits.get(t) == 0) {
            permits.put(t, -1);
            conditions.get(t).await();
          }else if (permits.get(t) > 0) {
            permits.put(t, 0);
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          lock.unlock();
        }
      }
     
      public void unpark(Thread thread) {
        Thread t = thread;
        lock.lock();
        try {
          if (permits.get(t) == null)
            permits.put(t, 1);
          else if (permits.get(t) == -1) {
            permits.put(t, 0);
            conditions.get(t).signal();
          }else if (permits.get(t) == 0) {
            permits.put(t, 1);
          }
        }finally {
          lock.unlock();
        }
      }
    }

    JVM实现一瞥

    其实在JVM底层对于park和unpark的实现也是基于锁和条件变量的,只不过是用更加底层的操作系统和libc(linux操作系统)提供的API进行实现的。虽然API不一样,但是原理是相仿的,思想也相似。

    比如下面的就是JVM实现的unpark方法:

    void Parker::unpark() {
      int s, status;
      // 进行加锁操作 相当于 可重入锁的 lock.lock()
      status = pthread_mutex_lock(_mutex);
      assert (status == 0, "invariant");
      s = _counter;
      _counter = 1;
      if (s < 1) {
        // 如果许可证小于 1 进行下面的操作
        if (WorkAroundNPTLTimedWaitHang) {
          // 这行代码相当于 condition.signal() 唤醒线程
          status = pthread_cond_signal (_cond);
          assert (status == 0, "invariant");
          // 解锁操作 相当于可重入锁的 lock.unlock()
          status = pthread_mutex_unlock(_mutex);
          assert (status == 0, "invariant");
        } else {
          status = pthread_mutex_unlock(_mutex);
          assert (status == 0, "invariant");
          status = pthread_cond_signal (_cond);
          assert (status == 0, "invariant");
        }
      } else {
        // 如果有许可证 也就是 s == 1 那么不许要将线程挂起
        // 解锁操作 相当于可重入锁的 lock.unlock()
        pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
      }
    }

    JVM实现的park方法,如果没有许可证也是会将线程挂起的:

    Kaedah pelaksanaan Java LockSupport tulisan tangan

    Atas ialah kandungan terperinci Kaedah pelaksanaan Java LockSupport tulisan tangan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam