I have a small project to synchronize multiple (two classes: ships, cars with a few instances with shared bufor class called Harbour) threads at the same time. They will be performing certain action on it. But I can't start with that until I synchronized the threads named "cars" in the Harbour. The Harbour has limited capacity and if this capacity is reached the "car" threads should be waiting until they will get signal that there's a free space to enter. I've used Retrant Lock with Condition but it doesn't work as I think.
public class Harbour {
final Lock protectNr;
final Condition protectNrCon;
int capacity;
int nrOfCars;
public Harbour(int capacity) {
this.capacity = capacity;
this.protectNr = new ReentrantLock();
this.protectNrCon = protectNr.newCondition();
}
public void carEnterHarbour(String name) {
try {
protectNr.lock();
if (this.nrOfCars == this.capacity)
protectNrCon.await();
nrOfCars++;
System.out.println(name + " enters");
System.out.println("Number of cars:" + this.nrOfCars);
protectNr.unlock();
} catch (InterruptedException e) {
System.out.println("Error");
}
}
public void carLeavingHarbour(String name) {
try {
protectNr.lock();
this.nrOfCars--;
protectNrCon.signal();
System.out.println(name + " leaving");
System.out.println("Number of cars:" + this.nrOfCars);
} finally {
protectNr.unlock();
}
}
}
public class Car extends Thread {
Harbour harbour;
public Car(Harbour harbour, String name) {
super(name);
this.harbour = harbour;
}
public void run() {
for (int i = 0; i < 10; i++) {
harbour.carEnterHarbour(getName());
harbour.carLeavingHarbour(getName());
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
int harbourCapacity = 20;
final Harbour harbour = new Harbour(harbourCapacity);
int nrOfCars = 500;
Car[] cars = new Car[nrOfCars];
for (int i = 0; i < nrOfCars; i++)
cars[i] = new Car(harbour, "Car-" + i);
for (int i = 0; i < nrOfCars; i++)
cars[i].start();
for (int i = 0; i < nrOfCars; i++)
cars[i].join();
}
}
What I was expecting after executing this code:
Car-386 leaving
Number of cars:**19**
Car-300 enters
Number of cars:**20**
Car-300 leaving
Number of cars:**19**
What I got:
Car-386 leaving
Number of cars:**20**
Car-300 enters
Number of cars:**21**
Car-295 enters
Number of cars:**22**
I also try to change int capacity to volatile int capacity and add some busy waiting but didn't work at all.
It looks like Threads are not block on Condition and I wonder why is this happening?
The documentation for Condition warns that spurious wakeups might occur (emphasis mine):
When waiting upon a Condition, a "spurious wakeup" is permitted to occur, in general, as a concession to the underlying platform semantics. This has little practical impact on most application programs as a Condition should always be waited upon in a loop, testing the state predicate that is being waited for. An implementation is free to remove the possibility of spurious wakeups but it is recommended that applications programmers always assume that they can occur and so always wait in a loop.
Your code doesn't honor that warning.
Your carEnterHarbour() must take this possibility of spurious wakeups into account and needs
while(this.nrOfCars == this.capacity){
protectNrCon.await();
}
instead of the simple if statement.
Depending on your requirements it might be easier to use a Semaphore:
public class Harbour {
final Semaphore slots;
public Harbour(int capacity){
this.slots = new Semaphore(capacity);
}
public void carEnterHarbour(String name) {
try{
slots.acquire();
}catch (InterruptedException e){
System.out.println("Error");
}
}
public void carLeavingHarbour(String name) {
slots.release();
}
}
Note that when using a Semaphore you don't have those locks in place when entering / leaving the Harbour and therefore it is difficult to get that ordered "car entering" / "car leaving" output together with the number of currently available slots.
In the carEnterHarbour method, you are calling await() on the protectNrCon condition, which causes the current thread to wait until it is signaled. However, you are not calling signal() anywhere in the carEnterHarbour method. This means that once a thread enters the if block, it will always wait indefinitely on the condition.
You should consider calling signal() on the protectNrCon condition after you increment nrOfCars and print the message, so that other threads waiting on the condition can be unblocked.
Additionally, you should call await() in a loop to ensure that the thread waits until the condition is true, rather than waiting indefinitely. Here is an example of how you can modify the carEnterHarbour method:
public void carEnterHarbour(String name) {
try {
protectNr.lock();
while (this.nrOfCars == this.capacity) {
protectNrCon.await();
}
nrOfCars++;
System.out.println(name+" enters");
System.out.println("Number of cars:" + this.nrOfCars);
protectNrCon.signal();
} catch (InterruptedException e) {
System.out.println("Error");
} finally {
protectNr.unlock();
}
}
Related
I was trying to learn multi threading, and was trying out a simple producer/consumer pattern with wait and notify. When i split the pattern with two consume and one produce i get an ArrayIndexOutOfBounds Exception which is not clear. The issue does not occur always it occurs at times. I am using a I3 processor.
I have tried added an if block to check if the count variable is going below or above the declared size and still the issue persists.
private static Object key = new Object();
private static int[] buffer;
private volatile static Integer count;
static class Consumer {
void consume() {
synchronized (key) {
if (isEmpty()) {
try {
key.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[--count] = 0;
key.notify();
}
}
public boolean isEmpty() {
return count == 0;
}
}
static class Producer {
void produce() {
synchronized (key) {
if (isFull()) {
try {
key.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[count++] = 1;
key.notify();
}
}
public boolean isFull() {
return count == buffer.length;
}
}
public static void main(String[] args) throws InterruptedException {
buffer = new int[10];
count = 0;
Producer producer = new Producer();
Consumer consumer = new Consumer();
Runnable produce = () -> {
for (int i = 0; i < 1500; i++)
producer.produce();
System.out.println("Done producing");
};
Runnable consume = () -> {
for (int i = 0; i < 1300; i++)
consumer.consume();
System.out.println("Done consuming");
};
Thread producerWorker = new Thread(produce);
Thread consumerWorker = new Thread(consume);
producerWorker.start();
consumerWorker.start();
//consumerWorker.join();
Runnable checker = () -> {
System.out.println("Lanched Delayed Consumer");
for (int i = 0; i < 200; i++)
consumer.consume();
};
Thread delayedConsumer = new Thread(checker);
delayedConsumer.start();
producerWorker.join();
System.out.println("Data in Buffer " + count);
}
}
Expected it to be :
Lanched Delayed Consumer
Done consuming
Done producing
Data in Buffer 0
but got :
Lanched Delayed Consumer
Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10
at com.multi.thread.waitandnotify.WaitNotifyRunner$Consumer.consume(WaitNotifyRunner.java:27)
at com.multi.thread.waitandnotify.WaitNotifyRunner.lambda$1(WaitNotifyRunner.java:78)
at java.base/java.lang.Thread.run(Thread.java:835)
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10
at com.multi.thread.waitandnotify.WaitNotifyRunner$Producer.produce(WaitNotifyRunner.java:55)
at com.multi.thread.waitandnotify.WaitNotifyRunner.lambda$0(WaitNotifyRunner.java:73)
at java.base/java.lang.Thread.run(Thread.java:835)
Data in Buffer 0
The problem is that:
you have only one wait condition that you share for both the "full" and the "empty" state
you're running two consumer threads ("consumerWorker" and "delayedConsumer") and one producer ("producerWorker")
you're not re-checking the condition in a while-loop like you should
A possible thing that happens is:
Both consumer threads see that the buffer is empty
The producer thread puts one item in the buffer, and notifies.
A single consumer thread wakes up and processes one item. The buffer is now empty. It then notifies.
The second consumer thread wakes up (instead of the producer like you intended), doesn't check whether the buffer is empty, and tries to access the buffer at index -1.
You need to recheck the condition after you wake up from a wait call. Change the if to a while. Here's how to do that for the consumer; you need to do the same for the producer.
void consume() {
synchronized (key) {
while (isEmpty()) {
try {
key.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[--count] = 0;
key.notify();
}
}
That way the consumer won't get confused by the notify from the other consumer thread.
This is also "recommended" by the documentation of Object.wait:
The recommended approach to waiting is to check the condition being
awaited in a while loop around the call to wait, as shown in the
example below. Among other things, this approach avoids problems that
can be caused by spurious wakeups.
(I quoted "recommended" because it's required to do it this way in order to implement it correctly because wait can also spuriously wake up without a call to notify)
your buffer is of length 10
buffer = new int[10];
so it's obvious whenever your count variable's exceeds array capacity which is 10
private volatile static Integer count;
you will get ArrayIndexOutOfBoundsException
you are producing more and consuming less and consuming less and execution of produce/consume is not sequential , its more like round-robin.
I am pretty new to Multithreading programming. In my code threads are trying to acquire locks around few lines. The lines work pretty fine for few context switches but then it halts (probably a deadlock).
On the other hand if use synchronized block then all works fine.
I've four classes.
1. PetersonAlgorithm.java
package com.ashish.master;
public class PetersonAlgorithm {
boolean wantCS[] = {false, false};
int turn = 1;
public void requestCS(int i) {
System.out.println("Lock requested by the thread - " + i);
wantCS[i] = true;
turn = 1 - i;
while(wantCS[1-i] && turn == 1-i);
}
public void releaseCS (int i) {
wantCS[i] = false;
turn = i - 1;
System.out.println("Lock released by the thread - " + i);
}
}
If anyone feels that above algorithm is incorrect then let me know, and feel free to make suggestions.
2. Runner.java
package com.ashish.master;
public class Runner {
public static Incrementer runnableInstance = new Incrementer();
public static Thread inc1 = new Thread(runnableInstance, "0");
public static Thread inc2 = new Thread(runnableInstance, "1");
public static void main(String args[]) {
inc1.start();
inc2.start();
try{
inc1.join();
inc2.join();
} catch (InterruptedException ex) {
System.out.println("The threads have been interrupted while waiting for the join ---> " + ex.getMessage());
}
System.out.println("The total turns taken by incrementer are ----> " + runnableInstance.turns);
}
}
3. Incrementer.java - If synchronized block is used instead of the Peterson algorithm, everything works fine.
package com.ashish.master;
public class Incrementer implements Runnable {
public long turns = 0;
public PetersonAlgorithm pa = new PetersonAlgorithm();
#Override
public void run() {
System.out.println("Thread " + this.toString() + "started.....");
while(true) {
pa.requestCS(Integer.parseInt(this.toString()));
// synchronized(this) {
if(DataStore.data < 1000000) printCriticalSection();
else break;
// }
pa.releaseCS(Integer.parseInt(this.toString()));
}
}
public void printCriticalSection() {
System.out.println("The value of the number is increased by thread " +
this.toString() +" to --> " + DataStore.increase());
turns ++;
}
#Override
public String toString() {
return Thread.currentThread().getName();
}
}
4. DataStore.java A class to mock the data source -- simply increase the number
package com.ashish.master;
public class DataStore {
public static long data = 0L;
public static long increase() {
DataStore.data += 1;
return DataStore.data;
}
}
Your runnables never observe each other's monitors (wantCS and turn) as they have different instances... Each runnable needs to work with a same shared set of monitors!
Take the blue pill and make your PetersonAlgorithm variables static volatile with synchronized block access...
Or take the red pill and you create a Class for your flag monitors (wantCS) and for your indicator monitor (turn). Then just define your runnable with one "own flag", one "observed flag" and one "indicator". Both Runnables will have the same indicator instance (therefore needs to be synchronized) while the flag instances will be crossed (the own flag of R1 will be the observed flag of R2 and the own flag of R2 the observed flag of R1). You should synchronized the flag methods too as you don't want to have a flag raised or lowered while being observed.
Then few steps:
Runnables raise their Flag
Runnables turn the shared Indicator ( set to opponent runnable's id )
Wait if opponent's flag is raised and Indicator is set to opponent.
The non waiting opponent does its stuff then lowers its flag.
The waiting opponent stops waiting (opponent's flag has been lowered), does its stuff and lowers its flag.
Each of your runnable instances has its own PetersonAlgorithm instance. Thus, the two runnables don't know anything about each other and will both always get immediate access to the critical section. Try implementing your PetersonAlgorithm class as static class with static methods. Then change the lines
pa.requestCS(Integer.parseInt(this.toString()));
// ...
pa.releaseCS(Integer.parseInt(this.toString()));
into
PetersonAlgorithm.requestCS(Integer.parseInt(this.toString()));
// ...
PetersonAlgorithm.releaseCS(Integer.parseInt(this.toString()));
I want to solve dining philosophers problem using java semaphores but I'm stuck. Highest id chopstick should be available but it seems to be always taken and i don't know why. Can anyone tell me where I made mistake?
Fork class:
class Fork {
public static Semaphore fork = new Semaphore(1);
public int id;
Fork(int id) {
this.id = id;
}
public int getId() {
return id;
}
public boolean take() {
return fork.tryAcquire();
}
public void putDown() {
fork.release();
}}
Philosopher class:
class Philosopher extends Thread {
private Fork fork_low;
private Fork fork_high;
private String name;
Philosopher(Fork fork_low, Fork fork_high, String name) {
this.fork_low = fork_low;
this.fork_high = fork_high;
this.name = name;
}
public void run() {
try {
sleep(1000);
} catch (InterruptedException ex) {
}
while (true) {
eat();
}
}
private void eat(){
if(fork_low.take()){
if(fork_high.take()){
try {
sleep(2000); // eating;
} catch (InterruptedException ex) { }
fork_high.putDown();
fork_low.putDown();
}
else{
fork_low.putDown();
}
}
}}
Main:
public static void main(String[] args) {
String[] names = {"Plato", "Aristotle", "Cicero", "Confucius", "Eratosthenes"};
Fork[] fork = new Fork[5];
Philosopher[] philosopher = new Philosopher[5];
for (int i = 0; i < fork.length; i++) {
fork[i] = new Fork(i);
}
for (int i = 0; i < philosopher.length; i++) {
if (i != philosopher.length - 1) {
philosopher[i] = new Philosopher(fork[i], fork[i+1], names[i]);
philosopher[i].start();
} else {
philosopher[i] = new Philosopher(fork[0], fork[i], names[i]);
philosopher[i].start();
}
}
}
You have a deadlock, because the Semaphore is static in the Fork class, which is equivalent to only have one fork available. It works perfectly when you make the Semaphore not static (2 random philosophers running on the same time).
You can observe your threads working in JDK's build in tool jvisualvm.
Here is the same problem solved in C language with explaination
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
//if not used then gives warning for sleep used
//semaphore are basically designed to share the resources
// here the sem_t is the data type for the semaphore
sem_t room;//counting semaphore bcoz here only one instance of room butchair has 4
sem_t spoon[5]; //this is binary semaphore since every spoon has its own instance
void * philosopher(void *);
void eat(int);
int main()
{
int i;
int a[5];
pthread_t tid[5];// threads here are refrence to philosophers or diners bcoz we will have multiple users dining
sem_init(&room,0,4);
//1.pointer to declared semaphore
//2.pshared which has 0,1 value that is if 0 ->shared between threads
// if 1 ->shared between process
//3.value with whch u initalise the semaphore
for(i=0;i<5;i++){
//5 binary semaphore each for individual spoon
sem_init(&spoon[i],0,1);
}
for(i=0;i<5;i++){
a[i]=i;//allow 5 to enter at a time and deadlock occurs so let 4 of them in
pthread_create(&tid[i],NULL,philosopher,(void*)&a[i]);
//1.thread id 2.NULL 3.function 4.what you want to pass to the new thread
//here we pass the address of philosophers number to function
}
for(i=0;i<5;i++){
pthread_join(tid[i],NULL);
}
}
void * philosopher(void * num){
int phil=*(int *)num; //cast the number passed as void to integer
//put sem_wait on both semaphore room and spoon
sem_wait(&room);//checks if resource is available,if then allocates and blocks semaphore
// room is counting semaphore so any is alocated then it decrements the count of total semaphore and
// if all are allocated then it blocks thread and places it on queue untill resource is freed
printf("\nPhilospher number %d has sat on dining table\n",phil);
sem_wait(&spoon[phil]);
sem_wait(&spoon[(phil+1)%5]);
//spoon is binary so if value of semaphore is 1 it is changed to 0 which means semaphore is allocated and cannot be used
eat(phil);
sleep(2);
printf("\nPhilosopher %d has finished eating\n",phil);
//free the semaphores so others in thread can use resources
//returns +ve value on freeing semaphore
//for binary semaphore if queue is empty then change semaphore value to 1 if not empty then remove process from queue and
// get it ready for allocation
sem_post(&spoon[(phil+1)%5]);
sem_post(&spoon[phil]);
sem_post(&room);
}
void eat(int phil){
printf("\nPhilosopher %d is eating now\n",phil);
}
I have two threads doing calculation on a common variable "n", one thread increase "n" each time, another decrease "n" each time, when I am not using volatile keyword on this variable, something I cannot understand happens, sb there please help explain, the snippet is like follow:
public class TwoThreads {
private static int n = 0;
private static int called = 0;
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
n = 0;
called = 0;
TwoThreads two = new TwoThreads();
Inc inc = two.new Inc();
Dec dec = two.new Dec();
Thread t = new Thread(inc);
t.start();
t = new Thread(dec);
t.start();
while (called != 2) {
//System.out.println("----");
}
System.out.println(n);
}
}
private synchronized void inc() {
n++;
called++;
}
private synchronized void dec() {
n--;
called++;
}
class Inc implements Runnable {
#Override
public void run() {
inc();
}
}
class Dec implements Runnable {
#Override
public void run() {
dec();
}
}
}
1) What I am expecting is "n=0,called=2" after execution, but chances are the main thread can be blocked in the while loop;
2) But when I uncomment this line, the program when as expected:
//System.out.println("----");
3) I know I should use "volatile" on "called", but I cannot explain why the above happens;
4) "called" is "read and load" in working memory of specific thread, but why it's not "store and write" back into main thread after "long" while loop, if it's not, why a simple "print" line can make such a difference
You have synchronized writing of data (in inc and dec), but not reading of data (in main). BOTH should be synchronized to get predictable effects. Otherwise, chances are that main never "sees" the changes done by inc and dec.
You don't know where exactly called++ will be executed, your main thread will continue to born new threads which will make mutual exclusion, I mean only one thread can make called++ in each time because methods are synchronized, and you don't know each exactly thread will be it. May be two times will performed n++ or n--, you don't know this, may be ten times will performed n++ while main thread reach your condition.
and try to read about data race
while (called != 2) {
//System.out.println("----");
}
//.. place for data race, n can be changed
System.out.println(n);
You need to synchronize access to called here:
while (called != 2) {
//System.out.println("----");
}
I sugest to add getCalled method
private synchronized int getCalled() {
return called;
}
and replace called != 2 with getCalled() != 2
If you interested in why this problem occure you can read about visibility in context of java memory model.
This isn't homework for me, it's a task given to students from some university. I'm interested in the solution out of personal interest.
The task is to create a class (Calc) which holds an integer. The two methods add and mul should add to or multiply this integer.
Two threads are set-up simultaneously. One thread should call c.add(3) ten times, the other one should call c.mul(3) ten times (on the same Calc-object of course).
The Calc class should make sure that the operations are done alternatingly ( add, mul, add, mul, add, mul, ..).
I haven't worked with concurrency related problems a lot - even less with Java. I've come up with the following implementation for Calc:
class Calc{
private int sum = 0;
//Is volatile actually needed? Or is bool atomic by default? Or it's read operation, at least.
private volatile bool b = true;
public void add(int i){
while(!b){}
synchronized(this){
sum += i;
b = true;
}
}
public void mul(int i){
while(b){}
synchronized(this){
sum *= i;
b = false;
}
}
}
I'd like to know if I'm on the right track here. And there's surely a more elegant way to the while(b) part.
I'd like to hear your guys' thoughts.
PS: The methods' signature mustn't be changed. Apart from that I'm not restricted.
Try using the Lock interface:
class Calc {
private int sum = 0;
final Lock lock = new ReentrantLock();
final Condition addition = lock.newCondition();
final Condition multiplication = lock.newCondition();
public void add(int i){
lock.lock();
try {
if(sum != 0) {
multiplication.await();
}
sum += i;
addition.signal();
}
finally {
lock.unlock();
}
}
public void mul(int i){
lock.lock();
try {
addition.await();
sum *= i;
multiplication.signal();
} finally {
lock.unlock();
}
}
}
The lock works like your synchronized blocks. But the methods will wait at .await() if another thread holds the lock until .signal() is called.
What you did is a busy loop: you're running a loop which only stops when a variable changes. This is a bad technique because it makes the CPU very busy, instead of simple making the thread wait until the flag is changed.
I would use two semaphores: one for multiply, and one for add. add must acquire the addSemaphore before adding, and releases a permit to the multiplySemaphore when it's done, and vice-versa.
private Semaphore addSemaphore = new Semaphore(1);
private Semaphore multiplySemaphore = new Semaphore(0);
public void add(int i) {
try {
addSemaphore.acquire();
sum += i;
multiplySemaphore.release();
}
catch (InterrupedException e) {
Thread.currentThread().interrupt();
}
}
public void mul(int i) {
try {
multiplySemaphore.acquire();
sum *= i;
addSemaphore.release();
}
catch (InterrupedException e) {
Thread.currentThread().interrupt();
}
}
As others have said, the volatile in your solution is required. Also, your solution spin-waits, which can waste quite a lot of CPU cycles. That said, I can't see any problems as far as correctness in concerned.
I personally would implement this with a pair of semaphores:
private final Semaphore semAdd = new Semaphore(1);
private final Semaphore semMul = new Semaphore(0);
private int sum = 0;
public void add(int i) throws InterruptedException {
semAdd.acquire();
sum += i;
semMul.release();
}
public void mul(int i) throws InterruptedException {
semMul.acquire();
sum *= i;
semAdd.release();
}
volatile is needed otherwise the optimizer might optimize the loop to if(b)while(true){}
but you can do this with wait and notify
public void add(int i){
synchronized(this){
while(!b){try{wait();}catch(InterruptedException e){}}//swallowing is not recommended log or reset the flag
sum += i;
b = true;
notify();
}
}
public void mul(int i){
synchronized(this){
while(b){try{wait();}catch(InterruptedException e){}}
sum *= i;
b = false;
notify();
}
}
however in this case (b checked inside the sync block) volatile is not needed
Yes, volatile is needed, not because an assignment from a boolean to another is not atomic, but to prevent the caching of the variable such that its updated value is not visible to the other threads who are reading it. Also sum should be volatile if you care about the final result.
Having said this, it would probably be more elegant to use wait and notify to create this interleaving effect.
class Calc{
private int sum = 0;
private Object event1 = new Object();
private Object event2 = new Object();
public void initiate() {
synchronized(event1){
event1.notify();
}
}
public void add(int i){
synchronized(event1) {
event1.wait();
}
sum += i;
synchronized(event2){
event2.notify();
}
}
public void mul(int i){
synchronized(event2) {
event2.wait();
}
sum *= i;
synchronized(event1){
event1.notify();
}
}
}
Then after you start both threads, call initiate to release the first thread.
Hmmm. There are a number of problems with your solution. First, volatile isn't required for atomicity but for visibility. I won't go into this here, but you can read more about the Java memory model. (And yes, boolean is atomic, but it's irrelevant here). Besides, if you access variables only inside synchronized blocks then they don't have to be volatile.
Now, I assume that it's by accident, but your b variable is not accessed only inside synchronized blocks, and it happens to be volatile, so actually your solution would work, but it's neither idiomatic nor recommended, because you're waiting for b to change inside a busy loop. You're burning CPU cycles for nothing (this is what we call a spin-lock, and it may be useful sometimes).
An idiomatic solution would look like this:
class Code {
private int sum = 0;
private boolean nextAdd = true;
public synchronized void add(int i) throws InterruptedException {
while(!nextAdd )
wait();
sum += i;
nextAdd = false;
notify();
}
public synchronized void mul(int i) throws InterruptedException {
while(nextAdd)
wait();
sum *= i;
nextAdd = true;
notify();
}
}
The program is fully thread safe:
The boolean flag is set to volatile, so the JVM knows not to cache values and to keep write-access to one thread at a time.
The two critical sections locks on the current object, which means only one thread will have access at a time. Note that if a thread is inside the synchronized block, no thread can be in any other critical sections.
The above will apply to every instance of the class. For example if two instances are created, threads will be able to enter multiple critical sections at a time, but will be limited to one thread per instances, per critical section. Does that make sense?