JavaRush /Java 博客 /Random-ZH /互斥锁、监视器和信号量有什么区别

互斥锁、监视器和信号量有什么区别

已在 Random-ZH 群组中发布
你好!在学习 JavaRush 中的多线程时,您经常会遇到“互斥体”和“监视器”的概念。现在你能不偷看地回答它们有何不同吗?:) 互斥量、监视器和信号量之间有什么区别 - 1如果可以的话,干得好!如果没有(而且这种情况经常发生)——也就不足为奇了。“互斥体”和“监视器”的概念确实是相关的。而且,在网上看外部资源上关于多线程的讲座和视频时,你还会遇到另一个类似的概念——“信号量”。它的功能也很大程度上类似于监视器和互斥体。因此,让我们理解这三个术语,看几个例子,最后在我们的头脑中组织对它们之间有何不同的理解:)

互斥体

互斥体是用于同步线程的特殊对象。它“附加”到 Java 中的每个对象 - 你已经知道了:) 无论你使用标准类还是创建自己的类,都没有关系,并且CatDog所有类的所有对象都有一个互斥体。“mutex”这个名字来源于英文“MUTual EXclusion”——“互斥”,这完美地体现了它的目的。正如我们在之前的一讲中所说,互斥体的任务就是提供这样一种机制,使得在某一时刻只有一个线程可以访问一个对象。现实生活中互斥体的一个流行的类比是“厕所的例子”。当一个人进入厕所时,他从里面锁上门。厕所作为一个可以被多个线程访问的对象。厕所门上的锁是互斥锁的作用,外面排队的人是线程的作用。门上的锁是厕所互斥锁:它确保一次只有一个人可以进去。 互斥量、监视器和信号量之间有什么区别 - 2换句话说,一次只有一个线程可以处理共享资源。其他线程(人)尝试访问占用的资源将会失败。互斥体有几个重要的特性。 首先,只有两种状态是可能的——“空闲”和“忙碌”。这使得更容易理解它的工作原理:可以使用布尔变量true/false或二进制数字系统 1/0 来绘制平行线。 其次,国家不能被直接控制。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 to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
在标有该单词的代码块中synchronized,我们的对象的互斥体被捕获obj。好吧,捕获发生了,但是“防御机制”到底是如何实现的呢?为什么其他线程看到单词时synchronized不能进入块内?是监视器创建了保护机制!编译器将单词转换synchronized成几个特殊的代码片段。让我们再次回到我们的示例doSomething()并添加该方法:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       //logic that is only available to one thread at a time
       synchronized (obj) {

           /*выполнить важную работу, при которой доступ к an objectу
           должен быть только у одного потока*/
           obj.someImportantMethod();
       }
   }
}
以下是编译器转换此代码后我们的程序“幕后”将发生的情况:
public class Main {

   private Object obj = new Object();

   public void doSomething() throws InterruptedException {

       //...some logic available to all threads

       //логика, которая одновременно доступна только для одного потока:

       /*до тех пор, пока мьютекс an object занят -
       любой другой поток (кроме того, который его захватил), спит*/
       while (obj.getMutex().isBusy()) {
           Thread.sleep(1);
       }

       //пометить мьютекс an object How занятый
       obj.getMutex().isBusy() = true;

       /*выполнить важную работу, при которой доступ к an objectу
       должен быть только у одного потока*/
       obj.someImportantMethod();

       //освободить мьютекс an object
       obj.getMutex().isBusy() = false;
   }
}
当然,这个例子不是真实的。在这里,我们尝试使用类似 Java 的代码来反映此时 Java 机器内部发生的情况。然而,这个伪代码可以很好地理解块内的对象和线程实际发生的情况synchronized,以及编译器如何将这个词转换为几个对程序员“不可见”的命令。本质上,Java 中的监视器是使用单词 来表达的synchronized。所有出现的代码(而不是synchronized上一个示例中的单词)都是监视器。

信号

当你自己学习多线程时,你会遇到的另一个词是“信号量”。让我们弄清楚它是什么以及它与监视器和互斥锁有何不同。信号量是同步资源访问的一种手段。 它的特殊之处在于它在创建同步机制时使用了计数器。 计数器告诉我们有多少线程可以同时访问共享资源。 互斥量、监视器和信号量之间有什么区别 - 3Java 中的信号量由类表示Semaphore。创建信号量对象时,我们可以使用以下构造函数:
Semaphore(int permits)
Semaphore(int permits, boolean fair)
我们传递给构造函数:
  • int permits— 初始和最大计数器值。即有多少个线程可以同时访问一个共享资源;

  • boolean fair- 建立线程接收访问的顺序。如果fair= true,则按照等待线程请求的顺序授予访问权限。如果为false,则顺序将由线程调度程序确定。

使用信号量的一个典型例子是哲学家午餐问题
互斥量、监视器和信号量之间有什么区别 - 4
为了更好地理解,我们将稍微简化其术语。想象一下我们有 5 位哲学家需要午餐。同时,我们只有一张桌子,同时不能超过两个人。我们的任务是养活所有哲学家。他们都不应该挨饿,也不应该在试图入座时互相“阻挡”(我们必须避免僵局)。这就是我们的哲学家类的样子:
class Philosopher extends Thread {

   private Semaphore sem;

   // поел ли философ
   private boolean full = false;

   private String name;

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

   public void run()
   {
       try
       {
           // если философ еще не ел
           if (!full) {
               //Запрашиваем у семафора разрешение на выполнение
               sem.acquire();
               System.out.println (name + " садится за стол");

               // философ ест
               sleep(300);
               full = true;

               System.out.println (name + " поел! Он выходит из-за стола");
               sem.release();

               // философ ушел, освободив место другим
               sleep(300);
           }
       }
       catch(InterruptedException e) {
           System.out.println ("What-то пошло не так!");
       }
   }
}
这是运行我们的程序的代码:
public class Main {

   public static void main(String[] args) {

       Semaphore sem = new Semaphore(2);
       new Philosopher(sem,"Сократ").start();
       new Philosopher(sem,"Платон").start();
       new Philosopher(sem,"Аристотель").start();
       new Philosopher(sem,"Фалес").start();
       new Philosopher(sem,"Пифагор").start();
   }
}
我们创建了一个计数为2的信号量来满足只有两个哲学家可以同时吃饭的条件。也就是说,只有两个线程可以同时工作,因为我们的类Philosopher是继承自Thread!类acquire()和方法控制其权限计数器。该方法请求从信号量访问资源的权限。如果 counter > 0,则授予权限,并且计数器减 1。该方法“释放”先前授予的权限并将其返回到计数器(将信号量的授予计数器加 1)。当我们运行程序时我们会得到什么?问题解决了吗?我们的哲学家会在等待轮到他们的时候打架吗?:) 这是我们收到的控制台输出: 苏格拉底坐在桌子旁 柏拉图坐在 苏格拉底吃过的桌子旁!他离开了桌子。柏拉图已经吃过了!他离开桌子 亚里士多德在桌子旁坐下 毕达哥拉斯在 亚里士多德吃过的桌子旁坐下!他离开了毕达哥拉斯吃过的桌子!他离开了泰尔斯的桌子,坐在了泰尔斯吃过的 桌子旁边 !他离开了桌子, 我们成功了!尽管泰勒斯不得不独自用餐,但我认为他并没有生我们的气:) 您可能已经注意到互斥体和信号量之间的一些相似之处。 一般来说,它们有相同的目的:同步对某些资源的访问。 唯一的区别是对象的互斥体一次只能由一个线程获取,而信号量则使用线程计数器,其中多个线程可以同时访问该资源。这不仅仅是巧合的相似之处:) 事实上,互斥锁是一个单位信号量。也就是说,它是一个计数器初始设置为1的信号量。它也被称为“二进制信号量”,因为它的计数器只能有2个值——1(“空闲”)和0(“忙”)。就这样!正如您所看到的,一切都变得不再那么混乱了:) 现在,如果您想在 Internet 上更详细地研究多线程主题,那么您会更容易理解这些概念。下一课见! release()Semaphoreacquire()release()互斥量、监视器和信号量之间有什么区别 - 5
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION