From f543057ed2a59fba1ecb4f10599357e09a59eeee Mon Sep 17 00:00:00 2001 From: Srijan Mishra Date: Mon, 14 Apr 2025 22:41:17 +0530 Subject: [PATCH 1/3] feat: Implement Actor Model pattern #3232 --- actor-model/README.md | 52 +++++++++++++++ actor-model/etc/actor-model.urm.puml | 35 +++++++++++ ..._model-Actor_Model___UML_Class_Diagram.png | Bin 0 -> 47298 bytes actor-model/pom.xml | 48 ++++++++++++++ .../java/com/iluwatar/actormodel/Actor.java | 59 ++++++++++++++++++ .../com/iluwatar/actormodel/ActorSystem.java | 41 ++++++++++++ .../java/com/iluwatar/actormodel/App.java | 44 +++++++++++++ .../com/iluwatar/actormodel/ExampleActor.java | 32 ++++++++++ .../java/com/iluwatar/actormodel/Message.java | 43 +++++++++++++ 9 files changed, 354 insertions(+) create mode 100644 actor-model/README.md create mode 100644 actor-model/etc/actor-model.urm.puml create mode 100644 actor-model/etc/actor_model-Actor_Model___UML_Class_Diagram.png create mode 100644 actor-model/pom.xml create mode 100644 actor-model/src/main/java/com/iluwatar/actormodel/Actor.java create mode 100644 actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java create mode 100644 actor-model/src/main/java/com/iluwatar/actormodel/App.java create mode 100644 actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java create mode 100644 actor-model/src/main/java/com/iluwatar/actormodel/Message.java diff --git a/actor-model/README.md b/actor-model/README.md new file mode 100644 index 000000000000..4164bbc541a3 --- /dev/null +++ b/actor-model/README.md @@ -0,0 +1,52 @@ +# Actor Model + +## Intent + +The Actor Model is a concurrency pattern that treats "actors" as the fundamental units of computation. Each actor has its own state and behavior and interacts with other actors exclusively through message passing. + +## Explanation + +### Real-world Example + +Imagine a team of people (actors) working in an office. They don’t share the same brain (memory), but instead communicate by passing notes (messages). Each person reads one note at a time and responds accordingly. + +### Problem + +Managing concurrent behavior in a safe and scalable way is difficult, especially with shared memory and race conditions. + +### Solution + +Encapsulate state and behavior within individual actors that communicate through asynchronous messages. + +## Class Diagram + +![UML Diagram](etc/actor-model.png) + +## Participants + +- **Actor**: Base class that defines a mailbox and handles incoming messages sequentially. +- **Message**: Represents communication between actors. +- **ActorSystem**: Creates and manages actors. +- **PrinterActor**: Sample actor that prints incoming messages. + +## Applicability + +Use the Actor Model pattern when: + +- You need a simple and safe way to handle concurrency. +- You want to avoid thread synchronization issues like race conditions and deadlocks. +- You want each object to process messages independently. + +## Example + +```java +ActorSystem system = new ActorSystem(); +Actor printer = system.actorOf(new PrinterActor()); + +printer.send(new Message("Hello Actor!", null)); +printer.send(new Message("Another message", null)); + +Thread.sleep(1000); + +((PrinterActor) printer).stop(); +system.shutdown(); diff --git a/actor-model/etc/actor-model.urm.puml b/actor-model/etc/actor-model.urm.puml new file mode 100644 index 000000000000..020c1fc735a4 --- /dev/null +++ b/actor-model/etc/actor-model.urm.puml @@ -0,0 +1,35 @@ +@startuml actor-model + +title Actor Model - UML Class Diagram + +class ActorSystem { + +actorOf(actor: Actor): Actor + +shutdown(): void +} + +class Actor { + -mailbox: BlockingQueue + -active: boolean + +send(message: Message): void + +stop(): void + +run(): void + #onReceive(message: Message): void +} + +class ExampleActor { + +onReceive(message: Message): void +} + +class Message { + -content: String + -sender: Actor + +getContent(): String + +getSender(): Actor +} + +ActorSystem --> Actor : creates +Actor <|-- ExampleActor : extends +Actor --> Message : processes +ExampleActor --> Message : uses + +@enduml diff --git a/actor-model/etc/actor_model-Actor_Model___UML_Class_Diagram.png b/actor-model/etc/actor_model-Actor_Model___UML_Class_Diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c34d7a75fdeec17c2665cb7012233805ae7454 GIT binary patch literal 47298 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 GjQTf&f{mL1 literal 0 HcmV?d00001 diff --git a/actor-model/pom.xml b/actor-model/pom.xml new file mode 100644 index 000000000000..9d1d08402a5c --- /dev/null +++ b/actor-model/pom.xml @@ -0,0 +1,48 @@ + + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + actor-model + Actor Model + + + + + + diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java new file mode 100644 index 000000000000..fbaeb0a4ecf4 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java @@ -0,0 +1,59 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +public abstract class Actor implements Runnable { + private final BlockingQueue mailbox = new LinkedBlockingQueue<>(); + private volatile boolean active = + true; // always read from main memory and written back to main memory, + + // rather than being cached in a thread's local memory. To make it consistent to all Actors + + public void send(Message message) { + mailbox.add(message); // Add message to queue + } + + public void stop() { + active = false; // Stop the actor loop + } + + @Override + public void run() { + while (active) { + try { + Message message = mailbox.take(); // Wait for a message + onReceive(message); // Process it + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + // Child classes must define what to do with a message + protected abstract void onReceive(Message message); +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java new file mode 100644 index 000000000000..d70b11cfd669 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java @@ -0,0 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ActorSystem { + private final ExecutorService executor = Executors.newCachedThreadPool(); + + public Actor actorOf(Actor actor) { + executor.submit(actor); // Run the actor in a thread + return actor; + } + + public void shutdown() { + executor.shutdownNow(); // Stop all threads + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/App.java b/actor-model/src/main/java/com/iluwatar/actormodel/App.java new file mode 100644 index 000000000000..b2dad713f868 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.java @@ -0,0 +1,44 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +public class App { + public static void main(String[] args) throws InterruptedException { + ActorSystem system = new ActorSystem(); + Actor Srijan = system.actorOf(new ExampleActor()); + + /* Actor Srijan = new ExampleActor() ; + system.actorOf(Srijan); this is also acceptable + */ + + Srijan.send(new Message("Hello Actor!", Srijan)); + Srijan.send(new Message("Another message", Srijan)); + + Thread.sleep(1000); // Give time for messages to process + + Srijan.stop(); // Stop the actor gracefully + system.shutdown(); // Stop the actor system + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java new file mode 100644 index 000000000000..d845fddd4241 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java @@ -0,0 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +public class ExampleActor extends Actor { + @Override + protected void onReceive(Message message) { + System.out.println("Received :" + message.getContent()); + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java new file mode 100644 index 000000000000..98a34a211e0e --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java @@ -0,0 +1,43 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +public class Message { + private final String content; + private final Actor sender; + + public Message(String content, Actor sender) { + this.content = content; + this.sender = sender; + } + + public String getContent() { + return content; + } + + public Actor getSender() { + return sender; + } +} From 3ee44ea8364601dacae79e8be5e62919e2302fcb Mon Sep 17 00:00:00 2001 From: Srijan Mishra Date: Mon, 14 Apr 2025 22:44:19 +0530 Subject: [PATCH 2/3] feat: Implement Actor Model pattern #3232 --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index 13dd8eaa02b1..ee0c75e9d201 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ Java Design Patterns + abstract-document abstract-factory active-object @@ -243,6 +244,8 @@ virtual-proxy visitor backpressure + actor-model + From 11abf48a17ed889876766004b2b367946edae8f5 Mon Sep 17 00:00:00 2001 From: Srijan Mishra Date: Tue, 15 Apr 2025 14:20:57 +0530 Subject: [PATCH 3/3] feat: update Actor Model implementation with multi-actor logic #3251 --- .../java/com/iluwatar/actormodel/Actor.java | 8 +++++++- .../com/iluwatar/actormodel/ActorSystem.java | 14 ++++++++++++-- .../main/java/com/iluwatar/actormodel/App.java | 17 ++++++++--------- .../com/iluwatar/actormodel/ExampleActor.java | 17 ++++++++++++++++- .../com/iluwatar/actormodel/ExampleActor2.java | 17 +++++++++++++++++ .../java/com/iluwatar/actormodel/Message.java | 14 ++++++-------- 6 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java index fbaeb0a4ecf4..e003c0c99a2e 100644 --- a/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java @@ -24,10 +24,16 @@ */ package com.iluwatar.actormodel; +import lombok.Getter; +import lombok.Setter; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public abstract class Actor implements Runnable { + + @Setter + @Getter + private String actorId; private final BlockingQueue mailbox = new LinkedBlockingQueue<>(); private volatile boolean active = true; // always read from main memory and written back to main memory, @@ -44,7 +50,7 @@ public void stop() { @Override public void run() { - while (active) { + while ( active ) { try { Message message = mailbox.take(); // Wait for a message onReceive(message); // Process it diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java index d70b11cfd669..830ba5f65b59 100644 --- a/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java @@ -24,15 +24,25 @@ */ package com.iluwatar.actormodel; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; public class ActorSystem { private final ExecutorService executor = Executors.newCachedThreadPool(); + private final ConcurrentHashMap actorRegister = new ConcurrentHashMap<>(); + private final AtomicInteger idCounter = new AtomicInteger(0); - public Actor actorOf(Actor actor) { + public void startActor(Actor actor) { + String actorId = "actor-" + idCounter.incrementAndGet(); //Generate a new and unique ID + actor.setActorId(actorId); // assign the actor it's ID + actorRegister.put(actorId,actor); //Register and save the actor with it's ID executor.submit(actor); // Run the actor in a thread - return actor; + } + + public Actor getActorById(String actorId){ + return actorRegister.get(actorId); // Find by Id } public void shutdown() { diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/App.java b/actor-model/src/main/java/com/iluwatar/actormodel/App.java index b2dad713f868..c15d4d6007f1 100644 --- a/actor-model/src/main/java/com/iluwatar/actormodel/App.java +++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.java @@ -27,18 +27,17 @@ public class App { public static void main(String[] args) throws InterruptedException { ActorSystem system = new ActorSystem(); - Actor Srijan = system.actorOf(new ExampleActor()); - - /* Actor Srijan = new ExampleActor() ; - system.actorOf(Srijan); this is also acceptable - */ - - Srijan.send(new Message("Hello Actor!", Srijan)); - Srijan.send(new Message("Another message", Srijan)); + Actor srijan = new ExampleActor(system); + system.startActor(srijan); + Actor ansh = new ExampleActor2(system); + system.startActor(ansh); + ansh.send(new Message("Hello Srijan" , srijan.getActorId())); + srijan.send(new Message("Hello ansh!", srijan.getActorId())); Thread.sleep(1000); // Give time for messages to process - Srijan.stop(); // Stop the actor gracefully + srijan.stop(); // Stop the actor gracefully + ansh.stop(); system.shutdown(); // Stop the actor system } } diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java index d845fddd4241..ac537d77efb4 100644 --- a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java @@ -24,9 +24,24 @@ */ package com.iluwatar.actormodel; +import java.util.logging.Logger; + public class ExampleActor extends Actor { + private final ActorSystem actorSystem; + + public ExampleActor(ActorSystem actorSystem) { + this.actorSystem = actorSystem; + } + Logger logger = Logger.getLogger(getClass().getName()); + @Override protected void onReceive(Message message) { - System.out.println("Received :" + message.getContent()); + logger.info("[" +getActorId()+ "]" + "Received : " + message.getContent()); + + Actor sender = actorSystem.getActorById(message.getSenderId());// sender actor id + if(sender!=null && !message.getSenderId().equals(getActorId())) { + sender.send(new Message("I got your message ", getActorId())); + } + } } diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java new file mode 100644 index 000000000000..c319de4b867b --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java @@ -0,0 +1,17 @@ +package com.iluwatar.actormodel; + +import java.util.logging.Logger; + +public class ExampleActor2 extends Actor{ + private final ActorSystem actorSystem; + + public ExampleActor2(ActorSystem actorSystem) { + this.actorSystem = actorSystem; + } + Logger logger = Logger.getLogger(getClass().getName()); + + @Override + protected void onReceive(Message message) { + logger.info("[" + getActorId()+"]" + "Received : " +message.getContent()); + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java index 98a34a211e0e..71134239c867 100644 --- a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java @@ -26,18 +26,16 @@ public class Message { private final String content; - private final Actor sender; + private final String senderId; - public Message(String content, Actor sender) { + public Message(String content, String senderId) { this.content = content; - this.sender = sender; + this.senderId = senderId; } - public String getContent() { - return content; - } + public String getContent() { return content; } - public Actor getSender() { - return sender; + public String getSenderId() { + return senderId; } }