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 000000000000..a4c34d7a75fd Binary files /dev/null and b/actor-model/etc/actor_model-Actor_Model___UML_Class_Diagram.png differ 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..e003c0c99a2e --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java @@ -0,0 +1,65 @@ +/* + * 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 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, + + // 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..830ba5f65b59 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java @@ -0,0 +1,51 @@ +/* + * 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.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 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 + } + + public Actor getActorById(String actorId){ + return actorRegister.get(actorId); // Find by Id + } + + 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..c15d4d6007f1 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.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 App { + public static void main(String[] args) throws InterruptedException { + ActorSystem system = new ActorSystem(); + 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 + 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 new file mode 100644 index 000000000000..ac537d77efb4 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java @@ -0,0 +1,47 @@ +/* + * 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.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) { + 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 new file mode 100644 index 000000000000..71134239c867 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.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; + +public class Message { + private final String content; + private final String senderId; + + public Message(String content, String senderId) { + this.content = content; + this.senderId = senderId; + } + + public String getContent() { return content; } + + public String getSenderId() { + return senderId; + } +} 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 +