CodeGym /Java 博客 /随机的 /互斥量、监视器和信号量之间的区别
John Squirrels
第 41 级
San Francisco

互斥量、监视器和信号量之间的区别

已在 随机的 群组中发布
你好!当您在 CodeGym 上学习多线程时,您经常会遇到“互斥”和“监视器”的概念。不用偷看,你能说出它们有何不同吗?:) 如果是,干得好!如果不是(这是最常见的),那就不足为奇了。“Mutex”和“monitor”其实是相关的概念。此外,当您在其他网站上阅读有关多线程的课程和观看视频时,您会遇到另一个类似的概念:“信号量”。它还具有与监视器和互斥锁非常相似的功能。这就是我们要研究这三个术语的原因。我们将通过几个示例来明确理解这些概念之间的区别 :)

互斥锁

互斥量(或锁)是一种用于同步线程的特殊机制。一个是“附加”到 Java 中的每个对象 — 您已经知道 :) 使用标准类或创建自己的类都没有关系,例如CatDog所有类的所有对象都有一个 mutex。“mutex”一词来自“MUTual EXclusion”,完美地描述了它的用途。正如我们在之前的一节课中所说的那样,互斥量可以确保一次只有一个线程可以访问该对象。 互斥量的一个流行的现实生活示例涉及厕所。当一个人进入厕所隔断时,他从里面锁上门。厕所就像一个可以被多个线程访问的对象。隔断门上的锁就像一个互斥量,外面的人排队代表线程。门上的锁是厕所的互斥锁:它确保只有一个人可以进入。 互斥量、监视器和信号量之间有什么区别? - 2换句话说,一次只有一个线程可以使用共享资源。其他线程(人)尝试访问已占用的资源将失败。互斥锁有几个重要的特性。 首先,只有两种状态是可能的:“解锁”和“锁定”。这有助于我们理解它是如何工作的:您可以与布尔变量 (true/false) 或二进制数 (0/1) 进行比较。 , 状态不能被直接控制。Java 没有任何机制可以让您显式获取对象、获取其互斥量并分配所需的状态。换句话说,你不能做这样的事情:

Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
这意味着您不能释放对象的互斥体。只有 Java 机器可以直接访问它。程序员通过语言的工具使用互斥体。

监视器

监视器是互斥体之上的附加“上层结构”。事实上,监视器是一段对程序员“不可见”的代码。 前面讲互斥锁的时候,我们举了一个简单的例子:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
在标有synchronized关键字 的代码块中,获取了我们obj对象的mutex 。太好了,我们可以获得锁,但是“保护”究竟是如何提供的呢?当我们看到synchronized一词时,是什么阻止其他线程进入块?保护来自监视器!编译器将synchronized关键字转换成几段特殊的代码。再一次,让我们回到我们使用doSomething()方法的示例。我们将添加到它:

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time
       synchronized (obj) {

           /* Do important work that requires that the object
           be accessed by only one thread */
           obj.someImportantMethod();
       }
   }
}
以下是编译器转换此代码后“幕后”发生的情况:

public class Main {

   private Object obj = new Object();

   public void doSomething() throws InterruptedException {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time:
     
       /* as long as the object's mutex is busy,
       all the other threads (except the one that acquired it) are put to sleep */
       while (obj.getMutex().isBusy()) {
           Thread.sleep(1);
       }

       // Mark the object's mutex as busy
       obj.getMutex().isBusy() = true;

       /* Do important work that requires that the object
       be accessed by only one thread */
       obj.someImportantMethod();

       // Free the object's mutex
       obj.getMutex().isBusy() = false;
   }
}
当然,这不是一个真实的例子。在这里,我们使用类似 Java 的代码来描述 Java 机器内部发生的事情。也就是说,这段伪代码很好地理解了同步内的对象和线程实际发生了什么,以及编译器如何将这个关键字转换成几个对程序员“不可见”的语句。基本上,Java 使用synchronized关键字来表示监视器。上例中出现的所有代替synchronized关键字的代码都是monitor。

信号

在个人学习多线程时会遇到的另一个词是“信号量”。让我们弄清楚这是什么以及它与监视器和互斥量有何不同。信号量是一种用于同步访问某些资源的工具。 它的显着特点是它使用计数器来创建同步机制。 计数器告诉我们有多少线程可以同时访问共享资源。 Java 中的信号量由Semaphore互斥量、监视器和信号量之间有什么区别? - 3类表示。在创建信号量对象时,我们可以使用以下构造函数:

Semaphore(int permits)
Semaphore(int permits, boolean fair)
我们将以下内容传递给构造函数:
    int permits — 计数器的初始值和最大值。也就是说,这个参数决定了有多少线程可以同时访问共享资源;
  • boolean fair — 确定线程获得访问权限的顺序。如果fair为真,则按照等待线程请求的顺序授予访问权限。如果为假,则顺序由线程调度程序确定。
信号量使用的一个典型例子是哲学家就餐问题。 互斥量、监视器和信号量之间有什么区别? - 4为了便于理解,我们稍微简化一下。想象一下,我们有 5 位哲学家需要吃午饭。此外,我们有一张桌子最多只能容纳两个人。我们的任务是养活所有的哲学家。他们都不应该饿着肚子,他们都不应该在试图坐在桌子旁时互相“阻挡”(我们必须避免僵局)。这是我们的哲学家课程的样子:

class Philosopher extends Thread {

   private Semaphore sem;

   // Did the philosopher eat?
   private boolean full = false;

   private String name;

   Philosopher(Semaphore sem, String name) {
       this.sem=sem;
       this.name=name;
   }

   public void run()
   {
       try
       {
           // If the philosopher has not eaten
           if (!full) {
               // Ask the semaphore for permission to run
               sem.acquire();
               System.out.println(name + " takes a seat at the table");

               // The philosopher eats
               sleep(300);
               full = true;

               System.out.println(name + " has eaten! He leaves the table");
               sem.release();

               // The philosopher leaves, making room for others
               sleep(300);
           }
       }
       catch(InterruptedException e) {
           System.out.println("Something went wrong!");
       }
   }
}
这是运行我们程序的代码:

public class Main {

   public static void main(String[] args) {

       Semaphore sem = new Semaphore(2);
       new Philosopher(sem, "Socrates").start();
       new Philosopher(sem,"Plato").start();
       new Philosopher(sem,"Aristotle").start();
       new Philosopher(sem, "Thales").start();
       new Philosopher(sem, "Pythagoras").start();
   }
}
我们创建了一个信号量,其计数器设置为 2 以满足条件:只有两个哲学家可以同时进餐。也就是只能同时运行两个线程,因为我们的Philosopher类继承了ThreadSemaphore类的acquire ()release()方法控制其访问计数器。acquire() 方法请求信号量访问资源。如果计数器 >0,则授予访问权限并将计数器减 1。release ()方法“释放”先前授予的访问权限,将其返回给计数器(将信号量的访问计数器增加 1)。当我们运行程序时我们得到了什么?问题解决了吗?我们的哲学家在等待轮到他们的时候不会战斗吗?:) 这是我们得到的控制台输出:

Socrates takes a seat at the table 
Plato takes a seat at the table 
Socrates has eaten! He leaves the table 
Plato has eaten! He leaves the table 
Aristotle takes a seat at the table 
Pythagoras takes a seat at the table 
Aristotle has eaten! He leaves the table 
Pythagoras has eaten! He leaves the table 
Thales takes a seat at the table 
Thales has eaten! He leaves the table 
我们做到了!尽管 Thales 不得不单独用餐,但我认为我们没有冒犯他 :) 您可能已经注意到互斥量和信号量之间的一些相似之处。 实际上,它们具有相同的使命:同步对某些资源的访问。 互斥量、监视器和信号量之间有什么区别? - 5唯一的区别是一个对象的互斥量一次只能由一个线程获取,而在使用线程计数器的信号量的情况下,多个线程可以同时访问该资源。这不仅仅是巧合 :) 互斥锁实际上是一个信号量计数为 1。换句话说,它是一个可以容纳单个线程的信号量。它也被称为“二进制信号量”,因为它的计数器只能有 2 个值——1(“解锁”)和 0(“锁定”)。就是这样!如您所见,它并没有那么令人困惑 :) 现在,如果您想在 Internet 上更详细地研究多线程,您将更容易理解这些概念。下节课见!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION