简体   繁体   中英

H2O producer java threads lock , reentrant lock

I want to produce h2o continuously by three threards first thread will produce h , second will produce h and third should produce o . How can I do it with lock ,consumer producer

        package com.threads.reentrantlock.consumerproducer;

        import java.util.concurrent.locks.Condition;
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;

        public class H2OProducer {
            static Lock lock = new ReentrantLock(true);
            static Condition condition = lock.newCondition();

            public static void main(String[] args) {
                try {
                    Thread h1 = new Thread(() -> {
                        try {
                            hydrogenProducer();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                    Thread h2 = new Thread(() -> {
                        try {
                            hydrogenProducer();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                    Thread o = new Thread(() -> {
                        try {
                            hydrogenProducer();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                    h1.start();
                    h2.start();
                    o.start();

                    try {
                        h1.join();
                        h2.join();
                        o.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } catch (Exception e) {
                }

            }

            public static void hydrogenProducer() throws InterruptedException {
                try {
                    lock.lock();
                        System.out.println("h");
                condition.signalAll();
            } finally {
                lock.unlock();
            }

        }

        public static void oxygenProducer() throws InterruptedException {
            try {
                lock.lock();
                System.out.println("o");
                    condition.signalAll();
                } finally {
                    lock.unlock();
                }
            }
        }

What I am doing wrong

Exception in thread "Thread-2" h java.lang.IllegalMonitorStateException at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151) at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261) at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457) at com.threads.reentrantlock.consumerproducer.H2OProducer.hydrogenProducer(H2OProducer.java:56) at com.threads.reentrantlock.consumerproducer.H2OProducer.lambda$2(H2OProducer.java:29) at java.lang.Thread.run(Thread.java:745)

You are signaling on a condition but there is no corresponding wait. Moreover, there is a typo - calling hydrogenProducer() from both threads (Thread o and Thread h )

I assume you want to produce two H s before producing O . It doesn't matter whether two H s is produced by the same thread or two different threads. I have used randomSleep() to demonstrate this situation.

import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class H2OProducer {
    static final int H2O_COUNT = 1_000;
    static final Random rand = new Random();

    static final Lock lock = new ReentrantLock(true);
    static final Condition oxzWait = lock.newCondition();
    static final Condition hydWait = lock.newCondition();

    static volatile int hydCount = 0;

    public static void main(String[] args) {
        try {
            Thread h1 = new Thread(() -> {
                try {
                    hydrogenProducer();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread h2 = new Thread(() -> {
                try {
                    hydrogenProducer();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            Thread o = new Thread(() -> {
                try {
                    oxygenProducer();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            h1.setName("H1-Producer");
            h2.setName("H2-Producer");
            o.setName("Ox-Producer");

            h1.start();
            h2.start();
            o.start();

            try {
                h1.join();
                h2.join();
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
        }

    }

    public static void hydrogenProducer() throws InterruptedException {
        for (int i = 0; i < H2O_COUNT; i++) {
            lock.lock();
            try {
                while (hydCount == 2) {
                    hydWait.await();
                }

                hydCount++;
                System.out.println(Thread.currentThread().getName()+ ": H produced - " + i);

                if (hydCount == 2) {
                    oxzWait.signalAll();
                }
            } finally {
                lock.unlock();
            }

            randomSleep();
        }
    }

    public static void oxygenProducer() throws InterruptedException {
        for (int i = 0; i < H2O_COUNT; i++) {
            lock.lock();
            try {
                while (hydCount < 2) {
                    oxzWait.await();
                }

            hydCount = 0;
            System.out.println(Thread.currentThread().getName()+ ": O produced - " + i);
            System.out.println("");
            hydWait.signalAll();
            } finally {
                lock.unlock();
            }

            randomSleep();
        }
    } 

    public static void randomSleep() {
        int ms = rand.nextInt(500);
        try { 
            Thread.sleep(ms);
        } catch (InterruptedException ex) {
        }
    }
}

However, if you want each H producer will produce one H for every H2O composition then you may look at CyclicBarrier . You can also do thread chaining if you need to maintain order, eg, T1 -> T2 -> T3 -> T1 -> T2 -> T3 ->.

The approach taken in the Java examples above forces the hydrogenProducer and the OxygenProducer to know how many items to produce before production begins. Another design approach centralizes the knowledge of when to stop producing by counting the final output of water molecules, which corresponds to the count of final production items in the "water factory". In a real manufacturing control system the decision about when to stop producing should be centralized, and not left to each individual component in the system.

The following example, written in Ada, exhibits this centralized control. Rather than employing signals to indicate that hydrogen or oxygen production has occurred, this solution actually passes symbolic elements of hydrogen and oxygen from the producers to the consumer which controls execution and counts final element production.

The Ada solution employs the Rendezvous mechanism allowing the producers to communicate directly with the consumer in a tightly controlled manner.

The producer task types are defined in a package named Elements. Unlike Java, Ada enforces a separation of interface and implementation. The interface for the Elements package is defined as:

   package Elements is
   type Element_Type is (Hydrogen, Oxygen);

   task type Hydrogen_Producer is
      Entry Stop;
      Entry Get_Element(Atom : out Element_Type);
   end Hydrogen_Producer;

   task type Oxygen_Producer is
      Entry Stop;
      Entry Get_Element(Atom : out Element_Type);
   end Oxygen_Producer;

   end Elements;

The type definition at the top of the Elements interface specification defines a data type named Element_Type with two values, Hydrogen and Oxygen. Two task types are defined, one for producing Hydrogen and one for producing Oxygen. Each of the task types has two entries. Entries are the mechanism allowing one task (or thread) to communicate directly with another task. The entry Stop tells the task when to stop executing. The entry Get_Element gets an instance of the element produced by the task.

The Rendezvous mechanism automatically synchronizes the task calling an entry with the called task. The implementation of the task types shows how the inter-task communication is performed.

with Ada.Numerics.Float_Random; use Ada.Numerics.Float_Random;

package body Elements is
   Seed : Generator;
   -----------------------
   -- Hydrogen_Producer --
   -----------------------

   task body Hydrogen_Producer is
      Element : constant Element_Type := Hydrogen;
   begin
      loop
         select
            accept Stop;
            exit;
         or
            accept Get_Element(Atom : out Element_Type) do
               Atom := Element;
            end Get_Element;
         end select;
         delay Duration(Random(Seed) * 0.1);
      end loop;
   end Hydrogen_Producer;

   ---------------------
   -- Oxygen_Producer --
   ---------------------

   task body Oxygen_Producer is
      Element : constant Element_Type := Oxygen;
   begin
      loop
         select
            accept Stop;
            exit;
         or
            accept Get_Element(Atom : out Element_Type) do
               Atom := Element;
            end Get_Element;
         end select;
         delay Duration(Random(Seed) * 0.1);
      end loop;
   end Oxygen_Producer;
begin
   reset(Seed);
end Elements;

Within the task body, where the task types are implemented, a variable named seed is declared. The variable seed is an instance of the type Generator defined in the package Ada.Numerics.Float_Random. This variable will hold the random number seed used to generate random delays for the producer tasks. The seed is initialized at the bottom of the task file before any of the producer tasks begin executing.

The two tasks are exactly the same except that Hydrogen_Producer only produces Hydrogen and Oxygen_Producer only produces Oxygen. Both tasks contain an infinite loop which is only interrupted when the Stop entry is called. Upon calling Stop the loop exit is commanded by the exit command. We also want to be able to get data from each producer, so that role is handled by accepting the Get_Element entry an passing out the element produced. Obviously we will either receive a Stop entry call, a Get_Element entry call or no entry call. The Select command allows our program to handle either Stop or Get_Element with no preference for one or the other. What happens when neither entry is called? The producer waits in the select block for one of the entries to be called, thus synchronizing execution with the caller.

We now need the equivalent of a "main" method to create an executable program. Ada allows the programmer to name the program entry point anything. It need not be named "main".

-----------------------------------------------------------------------
-- H2O production using 2 Hydrogen tasks and 1 Oxygen task
-----------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_IO;
with Elements; use Elements;

procedure Three_Task_H2O is
   H1 : Hydrogen_Producer;
   H2 : Hydrogen_Producer;
   Oxy : Oxygen_Producer;
   New_Atom    : Element_Type;
   Water_Count : natural := 0;

begin
   while Water_Count < 1000 loop
      H1.Get_Element(New_Atom);
      H2.Get_element(New_Atom);
      Oxy.Get_Element(New_Atom);
      Water_Count := Water_Count + 1;
      if Water_Count mod 20 = 0 then
         Put_Line("Water Produced:" & Water_Count'Image);
      end if;
   end loop;
   H1.Stop;
   H2.Stop;
   Oxy.Stop;
end Three_Task_H2o;

The procedure Three_Task_H2O creates two instances of Hydrogen_Producer named H1 and H2. It also creates an instance of Oxygen_Producer named Oxy. The tasks begin execution immediately. There is no equivalent of the thread.start syntax found in Java. Tree_Task_H2O loops while the count of water molecules is less than 1000. Each iteration of the loop calls the Get_Element entries for each producer. What happens if the producer is not ready? After all, each producer undergoes a random delay in producing its element. The result is that the calling (Consuming) task (Three_Task_H2O) is suspended until each entry call is handled. Information about the progress of water production is output every time another 20 water molecules is produced. When 1000 water molecules are produced the loop terminates and the Stop entries for all three tasks are called, terminating each task in order.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM