├── .gitignore ├── pom.xml ├── readme.md └── src ├── main ├── java │ └── com │ │ └── vgrazi │ │ └── jca │ │ ├── JCAFrame.java │ │ ├── JavaConcurrentAnimatedRebootApplication.java │ │ ├── Main.java │ │ ├── config │ │ └── Config.java │ │ ├── context │ │ ├── RelativePosition.java │ │ └── ThreadContext.java │ │ ├── engine │ │ └── AnimationEngine.java │ │ ├── slides │ │ ├── AtomicIntegerSlide.java │ │ ├── BasicSlide.java │ │ ├── BlockingQueueSlide.java │ │ ├── CompletableFutureSlide.java │ │ ├── CompletionServiceSlide.java │ │ ├── CountDownLatchSlide.java │ │ ├── CyclicBarrierSlide.java │ │ ├── ExecutorsSlide.java │ │ ├── IntroSlide.java │ │ ├── PhaserSlide.java │ │ ├── ReactorSlide.java │ │ ├── ReadWriteLockSlide.java │ │ ├── ReentrantLockSlide.java │ │ ├── SaturationPolicySlide.java │ │ ├── SemaphoreSlide.java │ │ ├── Slide.java │ │ ├── StampedLockSlide.java │ │ ├── SynchronizedSlide.java │ │ ├── TransferQueueSlide.java │ │ └── VirtualThreadsSlide.java │ │ ├── sprites │ │ ├── CompletableFutureSprite.java │ │ ├── FutureRunnableSprite.java │ │ ├── GetterThreadSprite.java │ │ ├── ObjectSprite.java │ │ ├── PhantomSprite.java │ │ ├── PooledThreadSprite.java │ │ ├── RunnableSprite.java │ │ ├── RunnerThreadSprite.java │ │ ├── Sprite.java │ │ ├── ThreadSprite.java │ │ ├── VirtualRunnerThreadSprite.java │ │ └── WriteThreadSprite.java │ │ ├── states │ │ ├── Blocked.java │ │ ├── Getting.java │ │ ├── Pooled.java │ │ ├── Retreating.java │ │ ├── Running.java │ │ ├── State.java │ │ ├── Terminated.java │ │ ├── ThreadState.java │ │ └── Waiting.java │ │ ├── util │ │ ├── HtmlUtils.java │ │ ├── IDGenerator.java │ │ ├── Logging.java │ │ ├── Parsers.java │ │ ├── RenderUtils.java │ │ ├── StringUtils.java │ │ ├── ThreadUtils.java │ │ └── UIUtils.java │ │ └── view │ │ ├── ButtonLayout.java │ │ ├── ButtonPanelLayout.java │ │ ├── ControlPanel.java │ │ ├── SnippetCanvas.java │ │ └── ThreadCanvas.java └── resources │ ├── application.properties │ ├── images │ ├── AtomicInteger.jpg │ ├── SpritesList.jpg │ ├── VisibilityAndSynchronization.png │ ├── blockingQueue.jpg │ ├── completionService.jpg │ ├── concurrent.jpg │ ├── concurrentText.jpg │ ├── countdownLatch.jpg │ ├── cover-slide.pptx │ ├── cursor.png │ ├── cyclicBarrier.jpg │ ├── executors.jpg │ ├── flag.png │ ├── future.jpg │ ├── mesh.png │ ├── reentrantLock.jpg │ └── reentrantRWLock.jpg │ └── snippets │ ├── atomic-integer.html │ ├── blocking-queue.html │ ├── completable-future.html │ ├── completion-service.html │ ├── countdown-latch.html │ ├── cyclic-barrier.html │ ├── executors.html │ ├── phaser.html │ ├── read-write-lock.html │ ├── reentrant-lock.html │ ├── semaphore.html │ ├── stamped-lock.html │ ├── synchronized.html │ ├── transfer-queue.html │ └── virtualthreads.html └── test ├── images ├── cover-slide-2.pptx └── neon-light-mesh-freezelight-abstraction.jpg └── java └── com └── vgrazi └── javaconcurrentanimated └── study ├── BlockingQueueStudy.java ├── CompletableFutureStudy.java ├── CompletionServiceStudy.java ├── CountdownLatchStudy.java ├── ExchangerStudy.java ├── ExecutorsStudy.java ├── Main.java ├── PhaserStudy.java ├── QueueStudy.java ├── ReactorStudy.java ├── ReentrantLockStudy.java ├── StampedLockStudy.java ├── StyleStudy.java ├── SynchronizedStudy.java └── TransferQueueStudy.java /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | HELP.md 4 | target/ 5 | !.mvn/wrapper/maven-wrapper.jar 6 | !**/src/main/** 7 | !**/src/test/** 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | 18 | ### IntelliJ IDEA ### 19 | .idea 20 | *.iws 21 | *.iml 22 | *.ipr 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | mvnw* 35 | .mvn/ 36 | /src/main/java/com/vgrazi/jca/play/ 37 | !/src/main/java/com/vgrazi/jca/play/Main.java 38 | !/src/main/java/com/vgrazi/jca/play/StylePlay.java 39 | #/src/test/java/com/vgrazi/javaconcurrentanimated/study/ 40 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.5 9 | 10 | 11 | com.vgrazi.jca 12 | java-concurrent-animated-reboot 13 | 0.0.1-SNAPSHOT 14 | java-concurrent-animated-reboot 15 | java-concurrent-animated-reboot 16 | 17 | UTF-8 18 | 21 19 | 21 20 | 21 | 5.11.3 22 | 3.13.0 23 | 1.3.0 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-devtools 35 | runtime 36 | true 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | io.micrometer 45 | micrometer-registry-atlas 46 | ${micrometer-registry-atlas.version} 47 | 48 | 49 | io.reactivex 50 | rxjava 51 | RELEASE 52 | 53 | 54 | io.reactivex 55 | rxjava-math 56 | RELEASE 57 | 58 | 59 | org.junit.jupiter 60 | junit-jupiter 61 | ${junit.version} 62 | test 63 | jar 64 | 65 | 66 | org.junit.jupiter 67 | junit-jupiter-api 68 | ${junit.version} 69 | test 70 | jar 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-maven-plugin 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-compiler-plugin 83 | ${maven-compiler-plugin.version} 84 | 85 | ${maven.compiler.source} 86 | ${maven.compiler.target} 87 | ${maven.compiler.source} 88 | ${maven.compiler.target} 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This is a the latest version of Java Concurrent Animated - 2 | (Original version binary download: https://sourceforge.net/projects/javaconcurrenta/) 3 | 4 | In the original version too much control was taken by the canvas, and all of the interactions were dependent on the slide program. 5 | 6 | In this reboot version, there is a single background thread per slide that carries all of the threads through the desired states. The renderer iterates through all of the threads, gets their state, current position, and shape, and renders them. 7 | 8 | Many of the original animations have been enhanced to provide more control over the underlying threads, and CompletableFuture has been added. 9 | 10 | Sprites List: 11 | 12 | ![image](src/main/resources/images/SpritesList.jpg) 13 | 14 | To run this - Clone the repo, build via Maven using mvn package, then double click the jar that is produced (under targets) 15 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/JavaConcurrentAnimatedRebootApplication.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | 6 | @SpringBootApplication 7 | public class JavaConcurrentAnimatedRebootApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplicationBuilder builder = new SpringApplicationBuilder(JavaConcurrentAnimatedRebootApplication.class); 11 | builder.headless(false).run(args); 12 | System.setProperty("java.util.logging.SimpleFormatter.format", 13 | "[%1$tF %1$tT] [%4$-7s] %5$s%n"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/Main.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca; 2 | 3 | import com.vgrazi.jca.context.ThreadContext; 4 | import com.vgrazi.jca.slides.*; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * Runs the Slide selected from the menu 12 | */ 13 | @Component 14 | public class Main implements CommandLineRunner { 15 | @Autowired 16 | private ThreadContext threadContext; 17 | 18 | @Autowired() 19 | private SynchronizedSlide synchronizedSlide; 20 | 21 | @Autowired() 22 | private ReentrantLockSlide reentrantLockSlide; 23 | 24 | @Autowired 25 | private ReadWriteLockSlide readWriteLockSlide; 26 | 27 | @Autowired 28 | private BlockingQueueSlide blockingQueueSlide; 29 | 30 | @Autowired 31 | private TransferQueueSlide transferQueueSlide; 32 | 33 | @Autowired 34 | private IntroSlide introSlide; 35 | 36 | @Autowired 37 | private ApplicationContext applicationContext; 38 | 39 | @Autowired 40 | private PhaserSlide phaserSlide; 41 | 42 | @Autowired 43 | private CyclicBarrierSlide cyclicBarrierSlide; 44 | 45 | @Autowired 46 | private CompletableFutureSlide completableFutureSlide; 47 | 48 | @Autowired 49 | private ExecutorsSlide executorsSlide; 50 | 51 | @Override 52 | public void run(String... args) { 53 | new Thread(() -> { 54 | try { 55 | threadContext.startAnimationThread(); 56 | } catch (InterruptedException e) { 57 | Thread.currentThread().interrupt(); 58 | } 59 | }).start(); 60 | String param; 61 | if(args.length == 0) { 62 | param = "intro"; 63 | } 64 | else { 65 | param = args[0]; 66 | } 67 | switch (param) { 68 | case "synchronized": 69 | threadContext.registerSlide(synchronizedSlide); 70 | break; 71 | case "reentrant-lock": 72 | threadContext.registerSlide(reentrantLockSlide); 73 | break; 74 | case "read-write-lock": 75 | threadContext.registerSlide(readWriteLockSlide); 76 | break; 77 | case "blocking-queue": 78 | threadContext.registerSlide(blockingQueueSlide); 79 | break; 80 | case "transfer-queue": 81 | threadContext.registerSlide(transferQueueSlide); 82 | break; 83 | case "phaser": 84 | threadContext.registerSlide(phaserSlide); 85 | break; 86 | case "completable-future": 87 | threadContext.registerSlide(completableFutureSlide); 88 | break; 89 | case "cyclic-barrier": 90 | threadContext.registerSlide(cyclicBarrierSlide); 91 | break; 92 | case "executors": 93 | threadContext.registerSlide(executorsSlide); 94 | break; 95 | case "intro": 96 | threadContext.registerSlide(introSlide); 97 | break; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.config; 2 | 3 | import com.vgrazi.jca.sprites.*; 4 | import com.vgrazi.jca.states.*; 5 | import com.vgrazi.jca.util.Parsers; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Scope; 10 | 11 | import javax.swing.*; 12 | import java.awt.*; 13 | 14 | @Configuration 15 | public class Config { 16 | 17 | @Bean 18 | @Scope("prototype") 19 | ThreadSprite threadSprite() { 20 | return new ThreadSprite(); 21 | } 22 | 23 | @Bean 24 | @Scope("prototype") 25 | ObjectSprite objectSprite() { 26 | return new ObjectSprite(); 27 | } 28 | 29 | @Bean 30 | @Scope("prototype") 31 | GetterThreadSprite getterSprite() { 32 | return new GetterThreadSprite(); 33 | } 34 | 35 | @Bean 36 | @Scope("prototype") 37 | WriteThreadSprite writeThreadSprite() { 38 | return new WriteThreadSprite(); 39 | } 40 | 41 | @Bean 42 | @Scope("prototype") 43 | CompletableFutureSprite completableFutureSprite() { 44 | return new CompletableFutureSprite(); 45 | } 46 | 47 | @Bean 48 | @Scope("prototype") 49 | RunnerThreadSprite runnerThreadSprite() { 50 | return new RunnerThreadSprite(); 51 | } 52 | 53 | @Bean 54 | @Scope("prototype") 55 | VirtualRunnerThreadSprite virtualRunnerThreadSprite() { 56 | return new VirtualRunnerThreadSprite(); 57 | } 58 | 59 | @Bean 60 | @Scope("prototype") 61 | PooledThreadSprite pooledThreadSprite() { 62 | return new PooledThreadSprite(); 63 | } 64 | 65 | @Bean 66 | @Scope("prototype") 67 | RunnableSprite runnableSprite() { 68 | return new RunnableSprite(); 69 | } 70 | 71 | @Bean 72 | @Scope("prototype") 73 | FutureRunnableSprite futureRunnableSprite() { 74 | return new FutureRunnableSprite(); 75 | } 76 | 77 | @Bean 78 | public Blocked blocked() { 79 | return new Blocked(); 80 | } 81 | 82 | @Bean 83 | public Running running() { 84 | return new Running(); 85 | } 86 | 87 | @Bean 88 | public Terminated terminated() { 89 | return new Terminated(); 90 | } 91 | 92 | @Bean 93 | public Waiting waiting() { 94 | return new Waiting(); 95 | } 96 | 97 | @Bean 98 | public Getting getting() { 99 | return new Getting(); 100 | } 101 | 102 | @Bean 103 | public Retreating retreating() { 104 | return new Retreating(); 105 | } 106 | 107 | @Bean 108 | public Stroke basicStroke() { 109 | return new BasicStroke(4); 110 | } 111 | 112 | @Bean Stroke writerStroke() { 113 | return new BasicStroke(3.0f, // Width 114 | BasicStroke.CAP_SQUARE, // End cap 115 | BasicStroke.JOIN_MITER, // Join style 116 | 10.0f, // Miter limit 117 | new float[]{2.0f, 4.0f}, // Dash pattern 118 | 0.0f); 119 | } 120 | 121 | @Bean 122 | public Stroke writerHeadStroke() { 123 | return new BasicStroke(1); 124 | } 125 | 126 | @Bean 127 | public Stroke dottedStroke() { 128 | return new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 0); 129 | } 130 | 131 | @Bean 132 | public Stroke dottedStroke1() { 133 | return new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 10); 134 | } 135 | 136 | @Bean 137 | public Stroke dottedStroke2() { 138 | return new BasicStroke(3, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[]{4}, 20); 139 | } 140 | 141 | @Bean 142 | public JLabel messages() { 143 | return new JLabel(); 144 | } 145 | 146 | @Bean 147 | /** 148 | * This is the card panel that flips to display 149 | * the animation or the slide intro 150 | */ 151 | public JPanel cardPanel() { 152 | JPanel cardPanel = new JPanel(new CardLayout()); 153 | return cardPanel; 154 | } 155 | 156 | @Bean 157 | public JLabel imageLabel() { 158 | JLabel imageLabel = new JLabel(); 159 | imageLabel.setBackground(Color.white); 160 | return imageLabel; 161 | } 162 | 163 | 164 | @Value("${message-font}") 165 | public void setMessageFont(String messageFont) { 166 | // Font font = Parsers.parseFont(messageFont); 167 | // messages().setFont(font); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/context/RelativePosition.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.context; 2 | 3 | /** 4 | * Represents the X position of the arrow relative to the monolith 5 | * The RelativePosition and the target State together determine the motion of the thread. 6 | */ 7 | public enum RelativePosition { 8 | Before, At, In, After 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/engine/AnimationEngine.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.engine; 2 | 3 | import com.vgrazi.jca.view.ThreadCanvas; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class AnimationEngine { 8 | 9 | public void render(ThreadCanvas canvas) { 10 | Thread thread = new Thread(() -> { 11 | while (true) { 12 | canvas.repaint(canvas.getBounds()); 13 | try { 14 | Thread.sleep(10); 15 | } catch (InterruptedException e) { 16 | Thread.currentThread().interrupt(); 17 | } 18 | } 19 | 20 | }); 21 | thread.start(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/AtomicIntegerSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.ThreadSprite; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | @Component 11 | public class AtomicIntegerSlide extends Slide { 12 | 13 | @Value("${monolith-right-border}") 14 | private int rightBorder; 15 | 16 | @Value("${monolith-left-border}") 17 | private int leftBorder; 18 | 19 | private ApplicationContext applicationContext; 20 | 21 | public AtomicIntegerSlide(ApplicationContext applicationContext) { 22 | this.applicationContext = applicationContext; 23 | } 24 | 25 | private AtomicInteger counter = new AtomicInteger(); 26 | public void run() { 27 | reset(); 28 | threadContext.addButton("getAndIncrement()", () -> { 29 | highlightSnippet(2); 30 | for (int i = 0; i < 5; i++) { 31 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("threadSprite"); 32 | sprite.attachAndStartRunnable(()-> { 33 | int count = counter.getAndIncrement(); 34 | while(true) { 35 | Thread.yield(); 36 | if(sprite.getXPosition() >= leftBorder -20) { 37 | sprite.setMessage(String.valueOf(count)); 38 | } 39 | 40 | if(sprite.getXPosition() >= rightBorder -20) { 41 | threadContext.stopThread(sprite); 42 | break; 43 | } 44 | } 45 | }, true); 46 | threadContext.addSprite(sprite); 47 | } 48 | }); 49 | ; 50 | 51 | threadContext.addButton("reset", this::reset); 52 | threadContext.setVisible(); 53 | } 54 | 55 | public void reset() { 56 | super.reset(); 57 | threadCanvas.setThinMonolith(); 58 | counter = new AtomicInteger(); 59 | threadContext.setSlideLabel("AtomicInteger"); 60 | setSnippetFile("atomic-integer.html"); 61 | setImage("images/AtomicInteger.jpg", .7f); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/BasicSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.*; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Use this as a starting point for constructing new slides. To use, uncomment the addButton for basicSlide in JCAFrame 10 | */ 11 | @Component 12 | public class BasicSlide extends Slide { 13 | 14 | @Value("${monolith-right-border}") 15 | private int rightBorder; 16 | 17 | @Value("${monolith-left-border}") 18 | private int leftBorder; 19 | 20 | private ApplicationContext applicationContext; 21 | 22 | public BasicSlide(ApplicationContext applicationContext) { 23 | this.applicationContext = applicationContext; 24 | } 25 | 26 | public void run() { 27 | reset(); 28 | threadContext.addButton("Take a bow", () -> { 29 | reset(); 30 | // create a new sprite 31 | ObjectSprite objectSprite = (ObjectSprite) applicationContext.getBean("objectSprite"); 32 | attachAndStartRunnable(objectSprite); 33 | objectSprite.setMessage("ObjectSprite"); 34 | threadContext.addSprite(objectSprite); 35 | threadContext.addYPixels(10); 36 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("threadSprite"); 37 | attachAndStartRunnable(sprite); 38 | sprite.setMessage("ThreadSprite"); 39 | threadContext.addSprite(sprite); 40 | threadContext.addYPixels(10); 41 | RunnerThreadSprite runnerThreadSprite = (RunnerThreadSprite) applicationContext.getBean("runnerThreadSprite"); 42 | attachAndStartRunnable(runnerThreadSprite); 43 | runnerThreadSprite.setMessage("RunnerThreadSprite"); 44 | threadContext.addSprite(runnerThreadSprite); 45 | threadContext.addYPixels(10); 46 | RunnableSprite runnableSprite = (RunnableSprite) applicationContext.getBean("runnableSprite"); 47 | attachAndStartRunnable(runnableSprite); 48 | runnableSprite.setMessage("RunnableSprite"); 49 | threadContext.addSprite(runnableSprite); 50 | threadContext.addYPixels(10); 51 | 52 | // FutureSprite futureSprite = (FutureSprite) applicationContext.getBean("completableFutureSprite"); 53 | // attachAndStartRunnable(futureSprite); 54 | // futureSprite.setMessage("FutureSprite"); 55 | // threadContext.addSprite(futureSprite); 56 | // threadContext.addYPixels(10); 57 | FutureRunnableSprite futureRunnableSprite = (FutureRunnableSprite) applicationContext.getBean("futureRunnableSprite"); 58 | attachAndStartRunnable(futureRunnableSprite); 59 | futureRunnableSprite.setMessage("FutureRunnableSprite"); 60 | threadContext.addSprite(futureRunnableSprite); 61 | threadContext.addYPixels(10); 62 | 63 | ThreadSprite interruptedSprite = (ThreadSprite) applicationContext.getBean("runnerThreadSprite"); 64 | interruptedSprite.attachAndStartRunnable(()->{ 65 | while(true){Thread.yield();} 66 | }, true); 67 | interruptedSprite.setMessage("InterruptedSprite"); 68 | interruptedSprite.getThread().interrupt(); 69 | threadContext.addSprite(interruptedSprite); 70 | threadContext.addYPixels(10); 71 | 72 | 73 | PooledThreadSprite pooledThreadSprite = (PooledThreadSprite) applicationContext.getBean("pooledThreadSprite"); 74 | pooledThreadSprite.setYPosition(threadContext.getNextPooledYPosition()); 75 | attachAndStartRunnable(pooledThreadSprite); 76 | pooledThreadSprite.setMessage("PooledThreadSprite"); 77 | threadContext.addSprite(pooledThreadSprite); 78 | threadContext.addYPixels(10); 79 | GetterThreadSprite getterThreadSprite = (GetterThreadSprite) applicationContext.getBean("getterSprite"); 80 | attachAndStartRunnable(getterThreadSprite); 81 | getterThreadSprite.setYPosition(threadContext.getNextYPosition()); 82 | getterThreadSprite.setMessage("GetterThreadSprite"); 83 | threadContext.addSprite(getterThreadSprite); 84 | threadContext.addYPixels(10); 85 | WriteThreadSprite writeThreadSprite = (WriteThreadSprite) applicationContext.getBean("writeThreadSprite"); 86 | attachAndStartRunnable(writeThreadSprite); 87 | writeThreadSprite.setMessage("WriteThreadSprite"); 88 | threadContext.addSprite(writeThreadSprite); 89 | 90 | // give it an action tag to test for 91 | runnerThreadSprite.setAction("someRunningTag"); 92 | // always attach a runnable, and then start the thread 93 | runnerThreadSprite.attachAndStartRunnable(()->{ 94 | while(runnerThreadSprite.getAction().equals("someRunningTag")) { 95 | // Even tho it is consuming CPU, we need to leave it running so as not to change the thread state 96 | // in any case, let it yield to running threads 97 | Thread.yield(); 98 | } 99 | threadContext.stopThread(runnerThreadSprite); 100 | }, true); 101 | // Always add the sprite to the thread context. 102 | threadContext.addSprite(runnerThreadSprite); 103 | }); 104 | threadContext.addButton("Stop thread", () -> { 105 | // get a running thread, if any 106 | ThreadSprite sprite = threadContext.getRunningThread(); 107 | // stop it 108 | if(sprite != null) { 109 | // set it to anything except "someRunningTag" so it will exit the loop above 110 | sprite.setAction("done"); 111 | } 112 | }); 113 | 114 | threadContext.addButton("reset", this::reset); 115 | threadContext.setVisible(); 116 | } 117 | 118 | private void attachAndStartRunnable(ThreadSprite sprite) { 119 | sprite.attachAndStartRunnable(()->{ 120 | Object mutex = new Object(); 121 | synchronized (mutex) { 122 | try { 123 | mutex.wait(); 124 | } catch (InterruptedException e) { 125 | e.printStackTrace(); 126 | } 127 | } 128 | }, true); 129 | } 130 | 131 | public void reset() { 132 | super.reset(); 133 | threadContext.setSlideLabel("Credits"); 134 | setSnippetFile("some.html"); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/CompletionServiceSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.FutureRunnableSprite; 4 | import com.vgrazi.jca.sprites.GetterThreadSprite; 5 | import com.vgrazi.jca.sprites.ThreadSprite; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | import java.util.Random; 10 | import java.util.concurrent.*; 11 | 12 | @Component 13 | public class CompletionServiceSlide extends Slide { 14 | 15 | private Random random = new Random(); 16 | 17 | private ExecutorService executor = Executors.newCachedThreadPool(); 18 | private CompletionService completionService; 19 | private ExecutorService pool; 20 | 21 | @Override 22 | public void run() { 23 | reset(); 24 | threadContext.addButton("submit()", () -> { 25 | highlightSnippet(1); 26 | // create a new sprite 27 | FutureRunnableSprite sprite = (FutureRunnableSprite) applicationContext.getBean("futureRunnableSprite"); 28 | // set to starting, so that it won't come up when we get all the runners when the complete button is pressed 29 | sprite.setAction("starting"); 30 | Future[] futureArray = new Future[1]; 31 | sprite.attachAndStartRunnable(() -> { 32 | futureArray[0] = completionService.submit(() -> { 33 | sprite.setFuture(futureArray[0]); 34 | sprite.setThread(Thread.currentThread()); 35 | sprite.setAction("running"); 36 | 37 | // attach a runnable and start the thread 38 | while (sprite.getAction().equals("running")) { 39 | // Even tho it is consuming CPU, we need to leave it running so as not to change the thread state 40 | // in any case, let it yield to running threads 41 | Thread.yield(); 42 | } 43 | 44 | return sprite; 45 | }); 46 | }, true); 47 | // Always add the sprite to the thread context. 48 | threadContext.addSprite(sprite); 49 | } 50 | ); 51 | 52 | threadContext.addButton("take().get()", () -> { 53 | executor.execute(() -> { 54 | highlightSnippet(2); 55 | GetterThreadSprite getter = (GetterThreadSprite) applicationContext.getBean("getterSprite"); 56 | threadContext.setGetterNextYPos(getter); 57 | threadContext.addSprite(getter); 58 | 59 | getter.attachAndStartRunnable(() -> { 60 | try { 61 | Future future = completionService.take(); 62 | if (future != null) { 63 | println("Called take. Now calling get"); 64 | FutureRunnableSprite originalSprite = (FutureRunnableSprite) future.get(); 65 | getter.setYPosition(originalSprite.getYPosition()); 66 | originalSprite.setDone(true); 67 | println("Completed get" + originalSprite); 68 | threadContext.stopThread(originalSprite); 69 | threadContext.stopThread(getter); 70 | } 71 | } catch (InterruptedException e) { 72 | e.printStackTrace(); 73 | } catch (ExecutionException e) { 74 | e.printStackTrace(); 75 | } 76 | }, true); 77 | }); 78 | }); 79 | 80 | threadContext.addButton("(complete)", () -> { 81 | // We need to find a not-done sprite, and mark it as done. 82 | List notCompleteSprites = threadContext.getThreadSpritesWithAction("running"); 83 | if (notCompleteSprites.size() > 0) { 84 | // pick a random one and complete it 85 | int index = random.nextInt(notCompleteSprites.size()); 86 | FutureRunnableSprite sprite = notCompleteSprites.get(index); 87 | sprite.setAction("complete"); 88 | // setting the sprite to "complete" will end the thread loop, thereby causing the future to be complete. 89 | // the rendering algorithm will leave that kissing the right side of the monolith, until it is marked as done 90 | } 91 | }); 92 | 93 | threadContext.addButton("reset", this::reset); 94 | } 95 | 96 | @Override 97 | public void reset() { 98 | super.reset(); 99 | threadContext.setSlideLabel("CompletionService"); 100 | pool = Executors.newFixedThreadPool(4); 101 | completionService = new ExecutorCompletionService<>(pool); 102 | setSnippetFile("completion-service.html"); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/CountDownLatchSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.ThreadSprite; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.concurrent.CountDownLatch; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @Component 13 | public class CountDownLatchSlide extends Slide { 14 | 15 | @Autowired 16 | private ApplicationContext applicationContext; 17 | 18 | @Value("${monolith-right-border}") 19 | private int rightBorder; 20 | 21 | private CountDownLatch countDownLatch = new CountDownLatch(4); 22 | private int totalCount = 4; 23 | 24 | public void run() { 25 | reset(); 26 | threadContext.addButton("await()", () -> { 27 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("threadSprite"); 28 | sprite.attachAndStartRunnable(() -> { 29 | try { 30 | highlightSnippet(1); 31 | countDownLatch.await(); 32 | threadContext.stopThread(sprite); 33 | } catch (InterruptedException e) { 34 | e.printStackTrace(); 35 | sprite.setMessage(e); 36 | sprite.setRetreating(); 37 | } 38 | }, true); 39 | threadContext.addSprite(sprite); 40 | }); 41 | threadContext.addButton("await(time, TimeUnit)", () -> { 42 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("threadSprite"); 43 | sprite.attachAndStartRunnable(() -> { 44 | boolean success; 45 | try { 46 | highlightSnippet(3); 47 | success = countDownLatch.await(3, TimeUnit.SECONDS); 48 | if(!success) { 49 | sprite.setRetreating(); 50 | } 51 | } catch (InterruptedException e) { 52 | e.printStackTrace(); 53 | sprite.setRetreating(); 54 | sprite.setMessage(e); 55 | } finally { 56 | // threadContext.stopThread(sprite); 57 | } 58 | }, true); 59 | threadContext.addSprite(sprite); 60 | }); 61 | threadContext.addButton("countDown()", () -> { 62 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("threadSprite"); 63 | sprite.attachAndStartRunnable(() -> { 64 | highlightSnippet(2); 65 | countDownLatch.countDown(); 66 | while(countDownLatch.getCount() >0 && sprite.getXPosition() < rightBorder-10) { 67 | Thread.yield(); 68 | } 69 | threadContext.stopThread(sprite); 70 | }, true); 71 | threadContext.addSprite(sprite); 72 | }); 73 | 74 | threadContext.addButton("interrupt", () -> { 75 | highlightSnippet(2); 76 | ThreadSprite sprite=threadContext.getFirstWaitingThread(); 77 | if(sprite != null) { 78 | sprite.getThread().interrupt(); 79 | } 80 | }); 81 | 82 | threadContext.addButton("reset", this::reset); 83 | threadContext.setVisible(); 84 | } 85 | 86 | public void reset() { 87 | super.reset(); 88 | threadCanvas.setThinMonolith(); 89 | threadContext.setSlideLabel("CountDownLatch"); 90 | setSnippetFile("countdown-latch.html"); 91 | setImage("images/countdownLatch.jpg", .7f); 92 | countDownLatch = new CountDownLatch(totalCount); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/CyclicBarrierSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.ThreadSprite; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.concurrent.BrokenBarrierException; 9 | import java.util.concurrent.CyclicBarrier; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.TimeoutException; 12 | 13 | @Component 14 | public class CyclicBarrierSlide extends Slide { 15 | 16 | @Autowired 17 | private ApplicationContext applicationContext; 18 | 19 | private CyclicBarrier cyclicBarrier; 20 | private ThreadSprite firstThread; 21 | private int count; 22 | public void run() { 23 | reset(); 24 | threadContext.addButton("await()", () -> createAwaitSprite("await", 1, false)); 25 | 26 | threadContext.addButton("await(time, TimeUnit)", () -> createAwaitSprite("await", 2, true)); 27 | 28 | threadContext.addButton("barrier.reset()", () ->{ 29 | setMessage(""); 30 | highlightSnippet(3); 31 | cyclicBarrier.reset(); 32 | initializeInstanceFields(); 33 | }); 34 | 35 | threadContext.addButton("reset", this::reset); 36 | threadContext.setVisible(); 37 | } 38 | 39 | private void createAwaitSprite(String action, int state, boolean timed) { 40 | setMessage(""); 41 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("threadSprite"); 42 | sprite.setAction(action); 43 | sprite.attachAndStartRunnable(() -> { 44 | try { 45 | highlightSnippet(state); 46 | if (firstThread == null) { 47 | firstThread = sprite; 48 | } 49 | else { 50 | sprite.setXPosition(firstThread.getXPosition()-20); 51 | } 52 | count++; 53 | if (timed) { 54 | cyclicBarrier.await(2, TimeUnit.SECONDS); 55 | } 56 | else { 57 | cyclicBarrier.await(); 58 | } 59 | 60 | threadContext.stopThread(sprite); 61 | count--; 62 | if(count == 0) { 63 | firstThread = null; 64 | } 65 | } catch (InterruptedException | BrokenBarrierException | TimeoutException e) { 66 | e.printStackTrace(); 67 | setMessage(e); 68 | sprite.setRetreating(); 69 | } 70 | finally { 71 | threadContext.stopThread(sprite); 72 | } 73 | }, true); 74 | threadContext.addSprite(sprite); 75 | } 76 | 77 | public void reset() { 78 | super.reset(); 79 | threadCanvas.setThinMonolith(); 80 | highlightSnippet(0); 81 | initializeInstanceFields(); 82 | threadContext.setSlideLabel("CyclicBarrier"); 83 | setSnippetFile("cyclic-barrier.html"); 84 | setImage("images/cyclicBarrier.jpg", .7f); 85 | setMessage(""); 86 | cyclicBarrier = new CyclicBarrier(4, ()->{ 87 | highlightSnippet(4); 88 | setMessage("Barrier Action Hit!!"); 89 | }); 90 | } 91 | 92 | private void initializeInstanceFields() { 93 | firstThread = null; 94 | messages.setText(""); 95 | count = 0; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/ExecutorsSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.PooledThreadSprite; 4 | import com.vgrazi.jca.sprites.RunnableSprite; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | 14 | @Component 15 | public class ExecutorsSlide extends Slide { 16 | @Autowired 17 | private ApplicationContext applicationContext; 18 | 19 | @Value("${pixels-per-y-step}") 20 | private int pixelsPerYStep; 21 | 22 | private ExecutorService executor; 23 | 24 | public void run() { 25 | reset(); 26 | 27 | threadContext.addButton("execute", () -> { 28 | RunnableSprite runnableSprite = (RunnableSprite) applicationContext.getBean("runnableSprite"); 29 | threadContext.addSprite(runnableSprite); 30 | highlightSnippet(2); 31 | 32 | executor.execute(() -> { 33 | Thread thread = Thread.currentThread(); 34 | PooledThreadSprite sprite = (PooledThreadSprite) threadContext.getThreadSprite(thread); 35 | if (sprite != null) { 36 | sprite.setPooled(false); 37 | sprite.setRunning(true); 38 | sprite.setYPosition(runnableSprite.getYPosition()); 39 | runnableSprite.setThread(thread); 40 | while (sprite.isRunning()) { 41 | Thread.yield(); 42 | } 43 | highlightSnippet(5); 44 | sprite.setPooled(true); 45 | runnableSprite.setDone(); 46 | threadContext.stopThread(runnableSprite); 47 | } else { 48 | printf("Thread %s not known to context%n", thread); 49 | } 50 | }); 51 | }); 52 | 53 | threadContext.addButton("submit", () -> { 54 | RunnableSprite runnableSprite = (RunnableSprite) applicationContext.getBean("runnableSprite"); 55 | threadContext.addSprite(runnableSprite); 56 | highlightSnippet(3); 57 | 58 | executor.submit(() -> { 59 | Thread thread = Thread.currentThread(); 60 | PooledThreadSprite sprite = (PooledThreadSprite) threadContext.getThreadSprite(thread); 61 | if (sprite != null) { 62 | sprite.setPooled(false); 63 | sprite.setRunning(true); 64 | sprite.setYPosition(runnableSprite.getYPosition()); 65 | runnableSprite.setThread(thread); 66 | while (sprite.isRunning()) { 67 | Thread.yield(); 68 | } 69 | highlightSnippet(6); 70 | sprite.setPooled(true); 71 | runnableSprite.setDone(); 72 | threadContext.stopThread(runnableSprite); 73 | } else { 74 | printf("Thread %s not known to context%n", thread); 75 | } 76 | }); 77 | }); 78 | 79 | threadContext.addButton("(done)", () -> { 80 | PooledThreadSprite sprite = threadContext.getRunningPooledThread(); 81 | if (sprite != null) { 82 | sprite.setRunning(false); 83 | sprite.setPooled(true); 84 | } 85 | }); 86 | 87 | threadContext.addButton("prestartAllCoreThreads()", () -> { 88 | ((ThreadPoolExecutor) executor).prestartAllCoreThreads(); 89 | highlightSnippet(4); 90 | }); 91 | 92 | threadContext.addButton("reset", this::reset); 93 | threadContext.setVisible(); 94 | } 95 | 96 | public void reset() { 97 | if (executor != null) { 98 | executor.shutdownNow(); 99 | } 100 | super.reset(); 101 | threadContext.setSlideLabel("Executors"); 102 | threadContext.setBottomLabel("Pooled\nThreads"); 103 | setSnippetFile("executors.html"); 104 | setImage("images/executors.jpg", .7f); 105 | 106 | executor = Executors.newFixedThreadPool(4, r -> { 107 | PooledThreadSprite sprite = (PooledThreadSprite) applicationContext.getBean("pooledThreadSprite"); 108 | Thread thread = new Thread(r); 109 | sprite.setThread(thread); 110 | sprite.setPooled(true); 111 | sprite.setRunning(false); 112 | threadContext.addSprite(sprite); 113 | return thread; 114 | }); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/IntroSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @Component 13 | public class IntroSlide extends Slide { 14 | 15 | @Autowired 16 | private JPanel cardPanel; 17 | private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 18 | public void run() { 19 | reset(); 20 | 21 | threadContext.addButton("reset()", this::reset); 22 | threadContext.setVisible(); 23 | // give it a few cycles to load the graphic 24 | executor.schedule(this::reset, 100, TimeUnit.MILLISECONDS); 25 | } 26 | @Override 27 | public void reset() { 28 | super.reset(); 29 | resetImage(); 30 | ((CardLayout) cardPanel.getLayout()).next(cardPanel); 31 | } 32 | 33 | public void resetImage() { 34 | setImage("images/mesh.png", 1); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/ReactorSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.PooledThreadSprite; 4 | import com.vgrazi.jca.sprites.RunnableSprite; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ThreadPoolExecutor; 13 | 14 | @Component 15 | public class ReactorSlide extends Slide { 16 | @Autowired 17 | private ApplicationContext applicationContext; 18 | 19 | @Value("${pixels-per-y-step}") 20 | private int pixelsPerYStep; 21 | 22 | private ExecutorService executor; 23 | 24 | public void run() { 25 | reset(); 26 | 27 | threadContext.addButton("prestartAllCoreThreads()", () -> { 28 | ((ThreadPoolExecutor) executor).prestartAllCoreThreads(); 29 | 30 | }); 31 | 32 | threadContext.addButton("submit", () -> { 33 | RunnableSprite runnableSprite = (RunnableSprite) applicationContext.getBean("runnableSprite"); 34 | threadContext.addSprite(runnableSprite); 35 | 36 | executor.submit(() -> { 37 | Thread thread = Thread.currentThread(); 38 | PooledThreadSprite sprite = (PooledThreadSprite) threadContext.getThreadSprite(thread); 39 | if (sprite != null) { 40 | sprite.setPooled(false); 41 | sprite.setRunning(true); 42 | sprite.setYPosition(runnableSprite.getYPosition()); 43 | runnableSprite.setThread(thread); 44 | while (sprite.isRunning()) { 45 | Thread.yield(); 46 | } 47 | sprite.setPooled(true); 48 | runnableSprite.setDone(); 49 | threadContext.stopThread(runnableSprite); 50 | } else { 51 | printf("Thread %s not known to context%n", thread); 52 | } 53 | }); 54 | }); 55 | 56 | threadContext.addButton("(done)", () -> { 57 | PooledThreadSprite sprite = threadContext.getRunningPooledThread(); 58 | if (sprite != null) { 59 | sprite.setRunning(false); 60 | sprite.setPooled(true); 61 | } 62 | }); 63 | 64 | threadContext.addButton("reset", this::reset); 65 | threadContext.setVisible(); 66 | } 67 | 68 | public void reset() { 69 | if (executor != null) { 70 | executor.shutdownNow(); 71 | } 72 | super.reset(); 73 | threadContext.setSlideLabel("Executors"); 74 | threadContext.setBottomLabel("Pooled\nThreads"); 75 | setSnippetFile("completion-service.html"); 76 | 77 | executor = Executors.newFixedThreadPool(4, r -> { 78 | PooledThreadSprite sprite = (PooledThreadSprite) applicationContext.getBean("pooledThreadSprite"); 79 | Thread thread = new Thread(r); 80 | sprite.setThread(thread); 81 | sprite.setPooled(true); 82 | sprite.setRunning(false); 83 | threadContext.addSprite(sprite); 84 | return thread; 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/ReadWriteLockSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.context.ThreadContext; 4 | import com.vgrazi.jca.sprites.ThreadSprite; 5 | import com.vgrazi.jca.sprites.WriteThreadSprite; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.awt.*; 11 | import java.util.concurrent.locks.ReadWriteLock; 12 | import java.util.concurrent.locks.ReentrantReadWriteLock; 13 | 14 | @Component 15 | public class ReadWriteLockSlide extends Slide { 16 | 17 | @Autowired 18 | private ApplicationContext applicationContext; 19 | 20 | @Autowired 21 | ThreadContext threadContext; 22 | 23 | private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 24 | 25 | @Autowired 26 | private Stroke basicStroke; 27 | 28 | public void run() { 29 | reset(); 30 | threadContext.addButton("readWriteLock.readLock().lock()", () -> { 31 | ThreadSprite sprite = (ThreadSprite) applicationContext.getBean("runnerThreadSprite"); 32 | highlightSnippet(1); 33 | sprite.setHolder("running"); 34 | sprite.attachAndStartRunnable(() -> { 35 | readWriteLock.readLock().lock(); 36 | String state="running"; 37 | spinDuringState(sprite, state); 38 | readWriteLock.readLock().unlock(); 39 | }, true); 40 | threadContext.addSprite(sprite); 41 | }); 42 | 43 | threadContext.addButton("readWriteLock.writeLock().lock()", () -> { 44 | ThreadSprite sprite = (WriteThreadSprite) applicationContext.getBean("writeThreadSprite"); 45 | sprite.setHolder("write-lock"); 46 | sprite.setSpecialId(1); 47 | sprite.attachAndStartRunnable(() -> { 48 | highlightSnippet(2); 49 | readWriteLock.writeLock().lock(); 50 | spinDuringState(sprite, "write-lock"); 51 | if ("downgrade".equals(sprite.getHolder())) { 52 | readWriteLock.readLock().lock(); 53 | readWriteLock.writeLock().unlock(); 54 | sprite.setStroke(basicStroke); 55 | sprite.setHolder("running"); 56 | spinDuringState(sprite, "running"); 57 | readWriteLock.readLock().unlock(); 58 | } else { 59 | readWriteLock.writeLock().unlock(); 60 | } 61 | threadContext.stopThread(sprite); 62 | 63 | }, true); 64 | threadContext.addSprite(sprite); 65 | }); 66 | 67 | threadContext.addButton("lock.unlock()", () -> { 68 | ThreadSprite runningThread = (ThreadSprite) threadContext.getRunningThread(); 69 | if (runningThread != null) { 70 | highlightSnippet(3); 71 | runningThread.setHolder("release"); 72 | threadContext.stopThread(runningThread); 73 | } 74 | }); 75 | 76 | threadContext.addButton("(downgrade to read)", () -> { 77 | ThreadSprite runningWriteThread = (ThreadSprite) threadContext.getFirstRunningThreadOfSpecialId(1); 78 | if (runningWriteThread != null) { 79 | highlightSnippet(4); 80 | runningWriteThread.setHolder("downgrade"); 81 | } 82 | }); 83 | 84 | threadContext.addButton("interrupt waiting", () -> { 85 | highlightSnippet(5); 86 | ThreadSprite sprite=threadContext.getFirstWaitingThread(); 87 | if(sprite != null) { 88 | sprite.getThread().interrupt(); 89 | } 90 | }); 91 | 92 | threadContext.addButton("interrupt running", () -> { 93 | highlightSnippet(5); 94 | ThreadSprite sprite=threadContext.getFirstNonInterruptedRunningThreadSprite(); 95 | if(sprite != null) { 96 | sprite.getThread().interrupt(); 97 | } 98 | }); 99 | 100 | threadContext.addButton("reset", this::reset); 101 | 102 | threadContext.setVisible(); 103 | } 104 | 105 | private void spinDuringState(ThreadSprite sprite, String state) { 106 | while(state.equals(sprite.getHolder())) { 107 | Thread.yield(); 108 | } 109 | } 110 | 111 | @Override 112 | public void reset() { 113 | super.reset(); 114 | threadContext.setSlideLabel("ReadWriteLock"); 115 | setSnippetFile("read-write-lock.html"); 116 | setImage("images/reentrantRWLock.jpg", .7f); 117 | readWriteLock = new ReentrantReadWriteLock(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/SaturationPolicySlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.PooledThreadSprite; 4 | import com.vgrazi.jca.sprites.RunnableSprite; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.concurrent.*; 10 | 11 | @Component 12 | public class SaturationPolicySlide extends Slide { 13 | 14 | @Value("${monolith-right-border}") 15 | private int rightBorder; 16 | 17 | @Value("${monolith-left-border}") 18 | private int leftBorder; 19 | 20 | private ApplicationContext applicationContext; 21 | 22 | private ThreadPoolExecutor executor; 23 | private SynchronousQueue workQueue; 24 | 25 | public SaturationPolicySlide(ApplicationContext applicationContext) { 26 | this.applicationContext = applicationContext; 27 | } 28 | 29 | public void run() { 30 | reset(); 31 | 32 | threadContext.addButton("execute", () -> { 33 | RunnableSprite runnableSprite = (RunnableSprite) applicationContext.getBean("runnableSprite"); 34 | threadContext.addSprite(runnableSprite); 35 | highlightSnippet(2); 36 | 37 | try { 38 | Runnable[] runnables = new Runnable[1]; 39 | runnables[0] = () -> { 40 | PooledThreadSprite sprite = (PooledThreadSprite) threadContext.getThreadSprite(Thread.currentThread()); 41 | if (sprite != null) { 42 | sprite.setPooled(false); 43 | sprite.setRunning(true); 44 | sprite.setYPosition(runnableSprite.getYPosition()); 45 | runnableSprite.setThread(Thread.currentThread()); 46 | while (sprite.isRunning()) { 47 | Thread.yield(); 48 | } 49 | sprite.setPooled(true); 50 | runnableSprite.setDone(); 51 | threadContext.stopThread(runnableSprite); 52 | highlightSnippet(0); 53 | } else { 54 | threadContext.stopThread(runnableSprite); 55 | runnableSprite.setRetreating(); 56 | printf("Thread %s not known to context%n", Thread.currentThread()); 57 | } 58 | }; 59 | executor.execute(runnables[0]); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | setMessage(e.getMessage()); 63 | threadContext.stopThread(runnableSprite); 64 | runnableSprite.setRetreating(); 65 | } 66 | }); 67 | 68 | threadContext.addButton("(done)", () -> { 69 | PooledThreadSprite sprite = threadContext.getRunningPooledThread(); 70 | if (sprite != null) { 71 | sprite.setRunning(false); 72 | sprite.setPooled(true); 73 | } 74 | }); 75 | 76 | threadContext.addButton("setRejectedExecutionHandler(CallerRuns)", ()->{ 77 | reset(); 78 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 79 | }); 80 | threadContext.addButton("setRejectedExecutionHandler(Discard)", ()->{ 81 | reset(); 82 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 83 | }); 84 | threadContext.addButton("setRejectedExecutionHandler(DiscardOldest)", ()->{ 85 | reset(); 86 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); 87 | }); 88 | threadContext.addButton("setRejectedExecutionHandler(Abort)", ()->{ 89 | reset(); 90 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); 91 | }); 92 | 93 | threadContext.addButton("reset", this::reset); 94 | threadContext.setVisible(); 95 | } 96 | 97 | 98 | public void reset() { 99 | if (executor != null) { 100 | executor.shutdownNow(); 101 | } 102 | super.reset(); 103 | setSnippetFile("executors.html"); 104 | 105 | threadContext.setSlideLabel("Saturation Policy"); 106 | // setSnippetFile("some.html"); 107 | workQueue = new SynchronousQueue<>(); 108 | executor = new ThreadPoolExecutor(0, 4, 2, TimeUnit.SECONDS, 109 | workQueue, 110 | r -> { 111 | PooledThreadSprite sprite = (PooledThreadSprite) applicationContext.getBean("pooledThreadSprite"); 112 | Thread thread = new Thread(r); 113 | sprite.setThread(thread); 114 | sprite.setPooled(true); 115 | sprite.setRunning(false); 116 | threadContext.addSprite(sprite); 117 | return thread; 118 | } 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/SemaphoreSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.ThreadSprite; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.concurrent.Semaphore; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import static com.vgrazi.jca.util.Logging.log; 13 | 14 | @Component 15 | public class SemaphoreSlide extends Slide { 16 | 17 | private final ApplicationContext applicationContext; 18 | 19 | public SemaphoreSlide(ApplicationContext applicationContext) { 20 | this.applicationContext=applicationContext; 21 | } 22 | 23 | private Semaphore semaphore=new Semaphore(4); 24 | private final Set alreadyTried = new HashSet<>(); 25 | 26 | public void run() { 27 | reset(); 28 | 29 | threadContext.addButton("semaphore.acquire()", () -> { 30 | ThreadSprite sprite=(ThreadSprite) applicationContext.getBean("runnerThreadSprite"); 31 | sprite.attachAndStartRunnable(() -> { 32 | threadContext.addSprite(sprite); 33 | highlightSnippet(1); 34 | try { 35 | log("About to acquire", sprite); 36 | semaphore.acquire(); 37 | printAvailablePermits(); 38 | 39 | log("acquired ", sprite); 40 | while(sprite.isRunning()) { 41 | Thread.yield(); 42 | } 43 | threadContext.stopThread(sprite); 44 | } catch(InterruptedException e) { 45 | Thread.currentThread().interrupt(); 46 | sprite.setRetreating(); 47 | } 48 | }, true); 49 | }); 50 | 51 | threadContext.addButton("semaphore.acquireUninterruptibly()", () -> { 52 | ThreadSprite sprite=(ThreadSprite) applicationContext.getBean("runnerThreadSprite"); 53 | sprite.attachAndStartRunnable(() -> { 54 | threadContext.addSprite(sprite); 55 | highlightSnippet(8); 56 | log("About to acquire", sprite); 57 | semaphore.acquireUninterruptibly(); 58 | printAvailablePermits(); 59 | 60 | log("acquired ", sprite); 61 | while(sprite.isRunning()) { 62 | Thread.yield(); 63 | } 64 | threadContext.stopThread(sprite); 65 | }, true); 66 | }); 67 | 68 | threadContext.addButton("semaphore.tryAcquire()", () -> { 69 | highlightSnippet(3); 70 | ThreadSprite sprite=(ThreadSprite) applicationContext.getBean("runnerThreadSprite"); 71 | sprite.attachAndStartRunnable(() -> { 72 | threadContext.addSprite(sprite); 73 | boolean b=semaphore.tryAcquire(); 74 | printAvailablePermits(); 75 | 76 | if(b) { 77 | log("acquired ", sprite); 78 | while(sprite.isRunning()) { 79 | Thread.yield(); 80 | } 81 | } else { 82 | sprite.setRetreating(); 83 | } 84 | threadContext.stopThread(sprite); 85 | }, true); 86 | }); 87 | 88 | threadContext.addButton("semaphore.tryAcquire(3, TimeUnit.SECONDS)", () -> { 89 | ThreadSprite sprite=(ThreadSprite) applicationContext.getBean("runnerThreadSprite"); 90 | highlightSnippet(4); 91 | sprite.attachAndStartRunnable(() -> { 92 | threadContext.addSprite(sprite); 93 | try { 94 | boolean b=semaphore.tryAcquire(3, TimeUnit.SECONDS); 95 | printAvailablePermits(); 96 | 97 | if(!b) { 98 | // todo: create a backoff rendering for threadSprite 99 | sprite.setRetreating(); 100 | } else { 101 | log("acquired ", sprite); 102 | while(sprite.isRunning()) { 103 | Thread.yield(); 104 | } 105 | } 106 | threadContext.stopThread(sprite); 107 | } catch(InterruptedException e) { 108 | Thread.currentThread().interrupt(); 109 | sprite.setRetreating(); 110 | } 111 | }, true); 112 | }); 113 | 114 | threadContext.addButton("semaphore.release()", () -> { 115 | ThreadSprite sprite=threadContext.getRunningThread(); 116 | highlightSnippet(2); 117 | semaphore.release(); 118 | printAvailablePermits(); 119 | if(sprite!=null) { 120 | sprite.attachAndStartRunnable(() -> { 121 | threadContext.stopThread(sprite); 122 | }, true); 123 | } 124 | }); 125 | 126 | threadContext.addButton("interrupt", () -> { 127 | highlightSnippet(7); 128 | ThreadSprite sprite=threadContext.getFirstWaitingThreadExcept(alreadyTried); 129 | if(sprite!=null){ 130 | sprite.getThread().interrupt(); 131 | alreadyTried.add(sprite); 132 | } 133 | }); 134 | 135 | 136 | threadContext.addButton("semaphore.drainPermits()", () -> { 137 | highlightSnippet(5); 138 | int count=semaphore.drainPermits(); 139 | printAvailablePermits(); 140 | }); 141 | 142 | threadContext.addButton("reset", this::reset); 143 | 144 | threadContext.setVisible(); 145 | 146 | } 147 | 148 | private void printAvailablePermits() { 149 | setMessage("Available permits:" + semaphore.availablePermits()); 150 | } 151 | 152 | public void reset() { 153 | super.reset(); 154 | alreadyTried.clear(); 155 | threadContext.setSlideLabel("Semaphore"); 156 | setSnippetFile("semaphore.html"); 157 | semaphore=new Semaphore(4); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/TransferQueueSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.GetterThreadSprite; 4 | import com.vgrazi.jca.sprites.ObjectSprite; 5 | import com.vgrazi.jca.sprites.ThreadSprite; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.concurrent.LinkedTransferQueue; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.TransferQueue; 14 | 15 | @Component 16 | public class TransferQueueSlide extends Slide { 17 | @Value("${monolith-left-border}") 18 | private int leftBorder; 19 | 20 | @Value("${monolith-right-border}") 21 | private int rightBorder; 22 | 23 | @Value("${pixels-per-y-step}") 24 | private int pixelsPerYStep; 25 | 26 | @Value("${arrow-length}") 27 | private int arrowLength; 28 | 29 | @Autowired 30 | private ApplicationContext applicationContext; 31 | 32 | private TransferQueue transferQueue; 33 | 34 | public void run() { 35 | reset(); 36 | 37 | threadContext.addButton("transferQueue.transfer(object)", () -> addAction(1, "transfer")); 38 | 39 | threadContext.addButton("transferQueue.tryTransfer(object)", () -> addAction(3, "try-transfer")); 40 | 41 | threadContext.addButton("transferQueue.tryTransfer(object, 2,TimeUnit.SECONDS)", () -> addAction(5, "try-timed-transfer")); 42 | 43 | threadContext.addButton("transferQueue.put()", () -> { 44 | highlightSnippet(6); 45 | ObjectSprite objectSprite = (ObjectSprite) applicationContext.getBean("objectSprite"); 46 | GetterThreadSprite getter = threadContext.getFirstGetterThreadSprite(); 47 | threadContext.addSprite(objectSprite); 48 | if (getter != null) { 49 | objectSprite.setYPosition(getter.getYPosition()); 50 | objectSprite.setXPosition(rightBorder - 10); 51 | } 52 | objectSprite.attachAndStartRunnable(() -> { 53 | try { 54 | transferQueue.put("xxx"); 55 | if (getter == null) { 56 | objectSprite.setAction("waiting"); 57 | while("waiting".equals(objectSprite.getAction())){ 58 | Thread.yield(); 59 | } 60 | } 61 | } catch (InterruptedException e) { 62 | Thread.currentThread().interrupt(); 63 | } 64 | threadContext.stopThread(objectSprite); 65 | if (getter != null) { 66 | threadContext.stopThread(getter); 67 | } 68 | 69 | }, true); 70 | }); 71 | 72 | threadContext.addButton("transferQueue.take()", () -> { 73 | // If there are waiting objects, don't create a new sprite 74 | highlightSnippet(2); 75 | ObjectSprite objectSprite = threadContext.getFirstWaitingObjectSprite(); 76 | 77 | ThreadSprite getter = (ThreadSprite) applicationContext.getBean("getterSprite"); 78 | if (objectSprite == null) { 79 | getter.attachAndStartRunnable(() -> { 80 | try { 81 | threadContext.setGetterNextYPos(getter); 82 | Object take = transferQueue.take(); 83 | 84 | println("Took " + take); 85 | } catch (InterruptedException e) { 86 | Thread.currentThread().interrupt(); 87 | } 88 | threadContext.stopThread(getter); 89 | }, true); 90 | } else { 91 | getter.attachAndStartRunnable(() -> { 92 | try { 93 | getter.setYPosition(objectSprite.getYPosition()); 94 | objectSprite.setXPosition(rightBorder - 20); 95 | objectSprite.setAction("done"); 96 | 97 | Object take = transferQueue.take(); 98 | println("Took " + take); 99 | } catch (InterruptedException e) { 100 | Thread.currentThread().interrupt(); 101 | } 102 | threadContext.stopThread(objectSprite); 103 | threadContext.stopThread(getter); 104 | }, true); 105 | } 106 | threadContext.addSprite(getter); 107 | } 108 | ); 109 | 110 | threadContext.addButton("Reset", this::reset); 111 | threadContext.setVisible(); 112 | } 113 | 114 | private void addAction(int state, String type) { 115 | highlightSnippet(state); 116 | ObjectSprite objectSprite = (ObjectSprite) applicationContext.getBean("objectSprite"); 117 | GetterThreadSprite getter = threadContext.getFirstGetterThreadSprite(); 118 | threadContext.addSprite(objectSprite); 119 | if (getter != null) { 120 | objectSprite.setYPosition(getter.getYPosition()); 121 | objectSprite.setXPosition(rightBorder - 10); 122 | } 123 | objectSprite.attachAndStartRunnable(() -> { 124 | if (type.equals("try-transfer")) { 125 | boolean success = transferQueue.tryTransfer("xxx"); 126 | if (!success) { 127 | objectSprite.setRetreating(); 128 | } 129 | } else if (type.equals("transfer")) { 130 | try { 131 | transferQueue.transfer("xxx"); 132 | } catch (InterruptedException e) { 133 | Thread.currentThread().interrupt(); 134 | } 135 | } else if (type.equals("try-timed-transfer")) { 136 | try { 137 | boolean success = transferQueue.tryTransfer("xxx", 2000, TimeUnit.MILLISECONDS); 138 | if (!success) { 139 | objectSprite.setRetreating(); 140 | } 141 | } catch (InterruptedException e) { 142 | Thread.currentThread().interrupt(); 143 | } 144 | } 145 | threadContext.stopThread(objectSprite); 146 | if (getter != null) { 147 | threadContext.stopThread(getter); 148 | } 149 | }, true); 150 | } 151 | 152 | public void reset() { 153 | super.reset(); 154 | threadContext.setSlideLabel("TransferQueue"); 155 | setSnippetFile("transfer-queue.html"); 156 | transferQueue = new LinkedTransferQueue(); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/slides/VirtualThreadsSlide.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.slides; 2 | 3 | import com.vgrazi.jca.sprites.ThreadSprite; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | @Component 14 | public class VirtualThreadsSlide extends Slide { 15 | 16 | @Autowired 17 | private ApplicationContext applicationContext; 18 | 19 | @Value("${monolith-left-border}") 20 | private int leftBorder; 21 | @Value("${arrow-length}") 22 | private int arrowLength; 23 | 24 | private int sleepMS = 2500; 25 | private final List sleeping = new ArrayList<>(); 26 | private final List waiting = new ArrayList<>(); 27 | private final List yield = new ArrayList<>(); 28 | private final List exit = new ArrayList<>(); 29 | private final List carriers = new ArrayList<>(); 30 | 31 | /** 32 | * The first thread we create will be contained in the future 33 | * Then every time we create a new future, we set firstThread = null 34 | * so that the next thread will be the first thread, to be used in the next future 35 | */ 36 | 37 | @Value("${pixels-per-y-step}") 38 | private int pixelsPerYStep; 39 | private AtomicInteger threadCount = new AtomicInteger(); 40 | 41 | public VirtualThreadsSlide() { 42 | } 43 | 44 | public void run() { 45 | reset(); 46 | threadContext.addButton("Thread.ofVirtual().start(()->someAction())", () -> addCreateAction(0)); 47 | 48 | threadContext.addButton("sleep(" + sleepMS + ")", () -> addSleepAction(1)); 49 | threadContext.addButton("synchronized(lock){lock.wait(" + sleepMS + ");}", () -> addWaitAction(2)); 50 | // threadContext.addButton("Thread.yield()", () -> addYieldAction(3)); 51 | threadContext.addButton("exit", () -> addExitAction(4)); 52 | 53 | threadContext.addButton("Reset", this::reset); 54 | 55 | threadContext.setVisible(); 56 | } 57 | 58 | private void addSleepAction(int state) { 59 | highlightSnippet(state); 60 | ThreadSprite runningThread = threadContext.getFirstNonWaitingThreadSprite(); 61 | sleeping.add(runningThread); 62 | } 63 | 64 | private void addWaitAction(int state) { 65 | highlightSnippet(state); 66 | ThreadSprite runningThread = threadContext.getFirstNonWaitingThreadSprite(); 67 | waiting.add(runningThread); 68 | } 69 | 70 | private void addYieldAction(int state) { 71 | highlightSnippet(state); 72 | ThreadSprite runningThread = threadContext.getRandomNonWaitingThreadSprite(); 73 | yield.add(runningThread); 74 | } 75 | private void addExitAction(int state) { 76 | highlightSnippet(state); 77 | ThreadSprite runningThread = threadContext.getFirstNonWaitingThreadSprite(); 78 | exit.add(runningThread); 79 | } 80 | 81 | 82 | /** 83 | * Called by the runAsynch and supplyAsync methods, adds a completableFutureSprite to the screen 84 | */ 85 | private void addCreateAction(int state) { 86 | highlightSnippet(state); 87 | ThreadSprite threadSprite = (ThreadSprite) applicationContext.getBean("virtualRunnerThreadSprite"); 88 | threadSprite.setXPosition(leftBorder + arrowLength); 89 | threadCount.incrementAndGet(); 90 | 91 | threadSprite.attachAndStartRunnable(() -> { 92 | while (!exit.contains(threadSprite)) { 93 | if(sleeping.contains(threadSprite)){ 94 | sleeping.remove(threadSprite); 95 | try { 96 | Thread.sleep(sleepMS); 97 | highlightSnippet(0); 98 | } catch(InterruptedException e) { 99 | throw new RuntimeException(e); 100 | } 101 | } 102 | if(waiting.contains(threadSprite)){ 103 | waiting.remove(threadSprite); 104 | try { 105 | synchronized(threadSprite) { 106 | threadSprite.wait(sleepMS); 107 | highlightSnippet(0); 108 | } 109 | } catch(InterruptedException e) { 110 | throw new RuntimeException(e); 111 | } 112 | } 113 | if(yield.contains(threadSprite)) { 114 | yield.remove(threadSprite); 115 | Thread.yield(); 116 | } 117 | } 118 | exit.remove(threadSprite); 119 | threadContext.stopThread(threadSprite); 120 | println(threadSprite + " exiting"); 121 | if(threadContext.getAllThreads().isEmpty()) 122 | threadCount.set(0); 123 | }, false); 124 | threadContext.addSprite(threadSprite); 125 | } 126 | 127 | 128 | public void reset() { 129 | super.reset(); 130 | highlightSnippet(0); 131 | threadCanvas.hideMonolith(true); 132 | threadContext.setSlideLabel("Virtual Threads "); 133 | threadContext.setSlideLabel(" Carrier", 1); 134 | threadCount.set(0); 135 | sleeping.clear(); 136 | carriers.clear(); 137 | waiting.clear(); 138 | yield.clear(); 139 | exit.clear(); 140 | setSnippetFile("virtualthreads.html"); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/CompletableFutureSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import org.springframework.beans.factory.InitializingBean; 4 | import org.springframework.beans.factory.annotation.Value; 5 | 6 | import java.awt.*; 7 | import java.util.concurrent.CompletableFuture; 8 | 9 | import static com.vgrazi.jca.util.Parsers.parseColor; 10 | import static com.vgrazi.jca.util.Parsers.parseFont; 11 | 12 | /** 13 | * A ThreadSprite represents one thread, and retains all of the state related to that thread, 14 | * including the Java thread itself, the shape, xPosition, and the action, which is called by slide, 15 | * and is used to change the state 16 | * Note: We should really create the thread in the constructor, but its Runnable needs access to this class's 17 | * running flag. So construct the sprite, then add the Runnable. 18 | */ 19 | public class CompletableFutureSprite extends Sprite implements InitializingBean { 20 | private Color futureDefaultColor; 21 | private Color futureDoneColor; 22 | private Color futureTextColor; 23 | private Font futureTextFont; 24 | @Value("${monolith-left-border}") 25 | private int leftBorder; 26 | 27 | @Value("${monolith-right-border}") 28 | private int rightBorder; 29 | 30 | @Value("${future-width}") 31 | private int width; 32 | 33 | private CompletableFuture future; 34 | @Value("${future-height}") 35 | private int height; 36 | private boolean isDisplayValue = true; 37 | 38 | public void setNextXPosition() { 39 | } 40 | 41 | public int getWidth() { 42 | return width; 43 | } 44 | 45 | public void setWidth(int width) { 46 | this.width = width; 47 | } 48 | 49 | public int getYCenter() { 50 | return getYPosition() - getYMargin() + (height + getYMargin() * 2)/2; 51 | } 52 | 53 | @Override 54 | public void render(Graphics2D graphics) { 55 | if (future != null) { 56 | graphics.setColor(Color.black); 57 | graphics.drawRect(getXPosition() + getXOffset() - getXMargin() , getYPosition() - getYMargin() -1, width + getXMargin() + getXRightMargin(), height + getYMargin() * 2); 58 | graphics.setColor(future.isDone() ? futureDoneColor : futureDefaultColor); 59 | graphics.fill3DRect(getXPosition() + getXOffset() - getXMargin() , getYPosition() - getYMargin() -1, width + getXMargin() + getXRightMargin(), height + getYMargin() * 2, true); 60 | renderLabel(graphics); 61 | if(future.isDone()) { 62 | String value=""; 63 | if (!future.isCompletedExceptionally()) { 64 | value = String.valueOf(future.join()); 65 | } 66 | else { 67 | value = "canceled"; 68 | } 69 | graphics.setColor(futureTextColor); 70 | graphics.setFont(futureTextFont); 71 | FontMetrics fm = graphics.getFontMetrics(); 72 | int xDelta = (width + getXMargin() + getXRightMargin() - fm.stringWidth(value))/2; 73 | int yDelta = (this.height + getYMargin() * 2 - fm.getHeight())/2; 74 | if (isDisplayValue()) { 75 | graphics.drawString(value,getXPosition() - getXMargin() + getXOffset() + xDelta,getYPosition() - getYMargin() -1 + this.height + yDelta); 76 | } 77 | } 78 | } 79 | } 80 | 81 | public void renderLabel(Graphics2D graphics) { 82 | if(getLabel() != null) { 83 | Graphics graphics1 = graphics.create(); 84 | graphics1.setColor(Color.yellow); 85 | graphics1.setFont(futureTextFont); 86 | graphics1.drawString(getLabel(), getXPosition() - getXMargin() + getXOffset() -60,getYPosition()/* - getYMargin() -1 + this.height*/); 87 | graphics1.dispose(); 88 | } 89 | } 90 | 91 | /** 92 | * If true, displays the value of this future, else doesn't 93 | */ 94 | public boolean isDisplayValue() { 95 | return isDisplayValue; 96 | } 97 | 98 | public void setDisplayValue(boolean displayValue) { 99 | isDisplayValue = displayValue; 100 | } 101 | 102 | @Value("${FUTURE-DEFAULT-COLOR}") 103 | private void setFutureDefaultColor(String color) { 104 | futureDefaultColor = parseColor(color); 105 | } 106 | 107 | @Value("${FUTURE-DONE-COLOR}") 108 | private void setFutureDoneColor(String color) { 109 | futureDoneColor = parseColor(color); 110 | } 111 | 112 | @Value("${FUTURE-TEXT-COLOR}") 113 | private void setFutureTextColor(String color) { 114 | futureTextColor = parseColor(color); 115 | } 116 | 117 | @Value("${FUTURE-TEXT-FONT}") 118 | private void setFutureTextFont(String font) { 119 | futureTextFont = parseFont(font); 120 | } 121 | 122 | @Override 123 | public void afterPropertiesSet() { 124 | setXPosition((rightBorder + leftBorder - width) / 2); 125 | } 126 | 127 | public int getHeight() { 128 | return height; 129 | } 130 | 131 | /** 132 | * Override the default height 133 | * @param height 134 | */ 135 | public void setHeight(int height) { 136 | this.height = height; 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | return "FutureSprite{" + 142 | "ID=" + getID() + 143 | // ", x-position=" + xPosition + 144 | ", x-offset=" + getXOffset() + 145 | // ", y-position=" + yPosition + 146 | ", relative_position=" + getRelativePosition() + 147 | '}'; 148 | } 149 | 150 | public CompletableFuture getFuture() { 151 | return future; 152 | } 153 | 154 | public void setFuture(CompletableFuture future) { 155 | this.future = future; 156 | } 157 | 158 | /** 159 | * Returns true of this future has been marked as complete 160 | * 161 | * @return 162 | */ 163 | public boolean isDone() { 164 | return future.isDone(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/FutureRunnableSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.states.ThreadState; 5 | import org.springframework.beans.factory.annotation.Value; 6 | 7 | import java.awt.*; 8 | import java.util.concurrent.Future; 9 | 10 | /** 11 | * This sprite renders as follows:
    12 | *
  • Before the monolith - a runnable with a thread racing towards the mutex
  • 13 | *
  • In the monolith (running) - renders like a runnable thread
  • 14 | *
  • In the monolith (complete) - renders as a stopped future
  • 15 | *
  • After the monolith - renders as an exiting future
  • 16 | *
17 | */ 18 | //public class FutureRunnableSprite extends ThreadSprite implements InitializingBean { 19 | public class FutureRunnableSprite extends RunnerThreadSprite{ 20 | 21 | private boolean done; 22 | 23 | @Value("${runnable-height}") 24 | private int runnableHeight; 25 | private Future future; 26 | 27 | @Override 28 | public void render(Graphics2D graphics) { 29 | int yPosition = getYPosition() - runnableHeight / 2; 30 | int length = arrowLength / 2; 31 | int xPosition; 32 | Future future = getFuture(); 33 | ThreadState state = getState(); 34 | // for FutureRunnableSprite, we need to abuse the state to control the rendering. 35 | // state machine: 36 | // before we enter, we are waiting 37 | // when we enter, we are runnable 38 | // when we complete, we are blocked 39 | // when we are done, we are terminated 40 | if (state == getThreadContext().waiting) { 41 | Color color = getThreadContext().getColor(this); 42 | graphics.setColor(color); 43 | RelativePosition relativePosition = getRelativePosition(); 44 | if( relativePosition == RelativePosition.Before) { 45 | super.render(graphics); 46 | graphics.fill3DRect(getXPosition() + getXOffset() - arrowLength + (arrowLength - length) / 2 - 3, yPosition, length, runnableHeight, true); 47 | } 48 | else if( relativePosition == RelativePosition.At) { 49 | xPosition = monolithLeftBorder; 50 | graphics.fill3DRect(xPosition + getXOffset() - arrowLength + (arrowLength - length) / 2 - 3, yPosition, length, runnableHeight, true); 51 | super.render(graphics); 52 | } 53 | } 54 | else if (state == getThreadContext().runnable) { 55 | super.render(graphics); 56 | RelativePosition relativePosition = getRelativePosition(); 57 | Color color = getThreadContext().getColor(this); 58 | graphics.setColor(color); 59 | // render the future 60 | if (relativePosition != RelativePosition.In) { 61 | xPosition = getXPosition() - arrowLength + (arrowLength - length) / 2 - 3; 62 | graphics.fill3DRect(xPosition + getXOffset(), yPosition, length, runnableHeight, true); 63 | } 64 | } 65 | else if(state == getThreadContext().blocked){ 66 | xPosition = monolithRightBorder - length; 67 | graphics.fill3DRect(xPosition + getXOffset(), yPosition, length, runnableHeight, true); 68 | } 69 | else { 70 | xPosition = getXPosition() - arrowLength + (arrowLength - length) / 2 - 3; 71 | graphics.fill3DRect(xPosition + getXOffset(), yPosition, length, runnableHeight, true); 72 | } 73 | } 74 | 75 | public boolean isDone() { 76 | return done; 77 | } 78 | 79 | public Future getFuture() { 80 | return future; 81 | } 82 | 83 | public void setFuture(Future future) { 84 | this.future = future; 85 | } 86 | 87 | public void setDone(boolean done) { 88 | this.done = done; 89 | } 90 | 91 | @Override 92 | public ThreadState getState() { 93 | // state is controlled by future.isDone() and Sprite.isDone() 94 | // if both are false, the the sprite is runnable 95 | // when the future is done, we still want to keep it in the monolith until the sprite is done 96 | // when the sprite is also done, we can terminate it 97 | if(future == null) { 98 | return getThreadContext().waiting; 99 | } 100 | else if(!future.isDone()) { 101 | // it's still alive 102 | return getThreadContext().runnable; 103 | } 104 | else if(!isDone()) { 105 | return getThreadContext().blocked; 106 | } 107 | else { 108 | return getThreadContext().terminated; 109 | } 110 | } 111 | 112 | @Override 113 | public String toString() { 114 | return "FutureRunnableSprite{" + 115 | "ID=" + getID() + 116 | ", isDone=" + isDone() + 117 | ", state=" + getState() + 118 | ", future=" + (future == null? null:"Done:" + future.isDone()) + 119 | ", native-state=" + (thread != null?thread.getState().toString():"") + 120 | // ", x-position=" + getXPosition() + 121 | ", y-position=" + getYPosition() + 122 | ", relative_position=" + getRelativePosition() + 123 | // ", " + super.toString() + 124 | '}'; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/GetterThreadSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.states.ThreadState; 5 | 6 | import java.awt.*; 7 | 8 | /** 9 | * This sprite renders a "getter" thread - a thread that called a potentially blocking get method 10 | */ 11 | public class GetterThreadSprite extends ThreadSprite { 12 | public GetterThreadSprite() { 13 | 14 | } 15 | 16 | public ThreadState getState() { 17 | Thread thread = getThread(); 18 | if (thread == null) { 19 | return null; 20 | } 21 | switch (thread.getState()) { 22 | case NEW: 23 | case WAITING: 24 | case TIMED_WAITING: 25 | case BLOCKED: 26 | case RUNNABLE: 27 | return getThreadContext().getting; 28 | case TERMINATED: 29 | if (getRelativePosition() == RelativePosition.After) 30 | return getThreadContext().terminated; 31 | else return getThreadContext().getting; 32 | default: 33 | throw new IllegalArgumentException("Unknown thread state " + thread.getState()); 34 | } 35 | } 36 | 37 | @Override 38 | protected int getNextYPositionFromContext() { 39 | return yPosition; 40 | } 41 | 42 | /** 43 | * Draws the ball at the end of the thread 44 | */ 45 | protected void drawThreadCap(Graphics2D graphics) { 46 | graphics.setColor(getThreadContext().getColorByInstance(this)); 47 | graphics.fillOval(getXPosition() + getXOffset() - 6 - arrowLength, getYPosition() - 5, 10, 10); 48 | } 49 | 50 | @Override 51 | public void setNextXPosition() { 52 | ThreadState state = getState(); 53 | // todo: center this in our future 54 | state.advancePosition(this); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/ObjectSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.states.ThreadState; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.beans.factory.annotation.Value; 7 | 8 | import java.awt.*; 9 | 10 | /** 11 | * A ThreadSprite represents one thread, and retains all of the state related to that thread, 12 | * including the Java thread itself, the shape, xPosition, and the action, which is called by slide, 13 | * and is used to change the state 14 | * Note: We should really create the thread in the constructor, but its Runnable needs access to this class's 15 | * running flag. So construct the sprite, then add the Runnable. 16 | */ 17 | public class ObjectSprite extends RunnerThreadSprite implements InitializingBean { 18 | @Value("${monolith-left-border}") 19 | private int leftBorder; 20 | 21 | @Value("${monolith-right-border}") 22 | private int rightBorder; 23 | 24 | @Value("${future-width}") 25 | private int width; 26 | 27 | private int height; 28 | 29 | @Override 30 | public void render(Graphics2D graphics) { 31 | graphics.setColor(Color.orange); 32 | int yCenter = getYPosition(); 33 | // Draw a positioning line, for diagnostics 34 | // graphics.drawLine(0, yCenter, 1000, yCenter); 35 | int yPos = yCenter - height / 2; 36 | graphics.fillOval(getXPosition() + getXOffset() - width, yPos + getYMargin(), width, height); 37 | // if the object is arriving or blocked, we draw the accompanying thread 38 | RelativePosition relativePosition = getRelativePosition(); 39 | if (relativePosition == RelativePosition.Before || relativePosition == RelativePosition.At) 40 | { 41 | Color color = getThreadContext().getColor(this); 42 | graphics.setColor(color); 43 | if (relativePosition != RelativePosition.In) { 44 | int xPosition = getXPosition(); 45 | int yPosition = getYPosition(); 46 | graphics.drawLine(xPosition - arrowLength + getXOffset() - width + 15, yPosition, xPosition + getXOffset() - width, yPosition); 47 | renderMessage(graphics); 48 | drawThreadCap(graphics, -width); 49 | } 50 | } 51 | } 52 | 53 | @Override 54 | public void setNextXPosition() { 55 | ThreadState state = getState(); 56 | if (state == getThreadContext().runnable && (getRelativePosition() == RelativePosition.In || getRelativePosition() == RelativePosition.At) && getDirection() == Direction.left) { 57 | super.setXPosition(rightBorder - 1 - getXMargin()); 58 | } else { 59 | state.advancePosition(this); 60 | } 61 | } 62 | 63 | @Override 64 | public void afterPropertiesSet() { 65 | super.afterPropertiesSet(); 66 | setXMargin((rightBorder - leftBorder - width) / 2); 67 | } 68 | 69 | /** 70 | * Override the default height 71 | * 72 | * @param height todo: add a specialized object height property 73 | */ 74 | @Value("${future-height}") 75 | public void setHeight(int height) { 76 | this.height = height / 2; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return "ObjectSprite{" + 82 | "ID=" + getID() + 83 | ", state=" + getState() + 84 | // ", native-state=" + thread.getState() + 85 | // ", x-position=" + getXPosition() + 86 | // ", y-position=" + getYPosition() + 87 | ", relative_position=" + getRelativePosition() + 88 | '}'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/PhantomSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import java.awt.*; 4 | 5 | /** 6 | * This sprite doesn't actually render anything 7 | * It just acts as a holder for some action 8 | */ 9 | public class PhantomSprite extends Sprite{ 10 | @Override 11 | public void setNextXPosition() { 12 | 13 | } 14 | 15 | @Override 16 | public void render(Graphics2D graphics) { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/PooledThreadSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | import java.awt.*; 7 | 8 | public class PooledThreadSprite extends RunnerThreadSprite { 9 | /** 10 | * If the sprite is pooled (ie not in action) then set to true. Otherwise it is active 11 | */ 12 | private boolean pooled = true; 13 | private int yPositionActive; 14 | private int yPositionPooled; 15 | @Autowired 16 | ApplicationContext context; 17 | 18 | /** 19 | * Sets the pooled status, and also re-initializes all of the positioning fields in the super class 20 | */ 21 | public void setPooled(boolean pooled) { 22 | this.pooled = pooled; 23 | // re-initialize all of the positioning fields in the super class, based on if we are rendering above or below 24 | if(pooled) { 25 | setYPosition(yPositionPooled); 26 | } 27 | else { 28 | setYPosition(yPositionActive); 29 | } 30 | } 31 | 32 | @Override 33 | public int getYPosition() { 34 | if(pooled) { 35 | return yPositionPooled; 36 | } 37 | else { 38 | return yPositionActive; 39 | } 40 | } 41 | 42 | @Override 43 | public void afterPropertiesSet() { 44 | super.afterPropertiesSet(); 45 | this.yPositionPooled = getThreadContext().getNextPooledYPosition(); 46 | 47 | // yPosition is already set by superclass 48 | this.yPositionActive = yPosition; 49 | } 50 | 51 | @Override 52 | protected void drawThreadCap(Graphics2D graphics) { 53 | if (getThreadState() == Thread.State.WAITING) { 54 | graphics.setColor(getThreadContext().getColorByInstance(this)); 55 | graphics.fillOval(leftBound + getXOffset(), topBound, ballDiameter, ballDiameter); 56 | 57 | } else { 58 | super.drawThreadCap(graphics); 59 | } 60 | } 61 | 62 | @Override 63 | protected int getNextYPositionFromContext() { 64 | // do nothing - y position is set from the runnable 65 | return 0; 66 | } 67 | 68 | public void setThread(Thread thread) { 69 | this.thread = thread; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return "PooledThreadSprite{" + 75 | "ID=" + getID() + 76 | ", state=" + getState() + 77 | ", native-state=" + thread.getState() + 78 | ", pooled=" + pooled + 79 | // ", x-position=" + getXPosition() + 80 | ", y-position=" + getYPosition() + 81 | ", relative_position=" + getRelativePosition() + 82 | '}'; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/RunnableSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.states.ThreadState; 4 | import org.springframework.beans.factory.annotation.Value; 5 | 6 | import java.awt.*; 7 | 8 | import static com.vgrazi.jca.util.Parsers.parseColor; 9 | 10 | public class RunnableSprite extends ThreadSprite { 11 | private Color futureDefaultColor; 12 | @Value("${monolith-left-border}") 13 | private int leftBorder; 14 | 15 | @Value("${monolith-right-border}") 16 | private int rightBorder; 17 | 18 | @Value("${future-width}") 19 | private int width; 20 | private int height; 21 | private boolean done; 22 | 23 | @Value("${pixels-per-y-step}") 24 | public void setHeight(int height) { 25 | this.height = height / 4; 26 | } 27 | 28 | @Override 29 | public void setNextXPosition() { 30 | ThreadState state = getState(); 31 | state.advancePosition(this); 32 | } 33 | 34 | @Override 35 | public void setXPosition(int xPosition) { 36 | if (getState() == getThreadContext().runnable && ((getDirection() == Direction.left || xPosition >= xRestingPos && xPosition <= rightBorder))) { 37 | super.setXPosition(xRestingPos); 38 | } else { 39 | super.setXPosition(xPosition); 40 | } 41 | } 42 | 43 | private int xRestingPos; 44 | 45 | @Override 46 | public void afterPropertiesSet() { 47 | super.afterPropertiesSet(); 48 | xRestingPos = rightBorder - (rightBorder - leftBorder - width) / 2; 49 | } 50 | 51 | @Override 52 | public void render(Graphics2D graphics) { 53 | // todo: make a property 54 | graphics.setColor(Color.orange); 55 | graphics.fill3DRect(getXPosition() + getXOffset() - width - 1, getYPosition() - height / 2, width, height, true); 56 | } 57 | 58 | @Value("${FUTURE-DEFAULT-COLOR}") 59 | private void setFutureDefaultColor(String color) { 60 | futureDefaultColor = parseColor(color); 61 | } 62 | 63 | @Override 64 | public ThreadState getState() { 65 | if(isRetreating()) { 66 | return getThreadContext().retreating; 67 | } 68 | else if (done) { 69 | return getThreadContext().terminated; 70 | } 71 | else if (thread == null) { 72 | return getThreadContext().waiting; 73 | } else { 74 | return super.getState(); 75 | } 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "RunnableSprite{" + 81 | "ID=" + getID() + 82 | ", state=" + getState() + 83 | ",done=" + done + 84 | ",retreating=" + isRetreating() + 85 | ",thread=" + thread + 86 | 87 | // ", x-position=" + getXPosition() + 88 | // ", y-position=" + yPosition + 89 | ", relative_position=" + getRelativePosition() + 90 | '}'; 91 | } 92 | 93 | public void setDone() { 94 | this.done = true; 95 | // set thread to null, so that our context search by thread for the pooled thread returns the pooled thread and not this runnable 96 | setThread(null); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/ThreadSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.states.ThreadState; 4 | import org.springframework.beans.factory.InitializingBean; 5 | import org.springframework.beans.factory.annotation.Value; 6 | 7 | import java.awt.*; 8 | import java.util.concurrent.locks.Condition; 9 | 10 | /** 11 | * A ThreadSprite represents one thread, and retains all of the state related to that thread, 12 | * including the Java thread itself, the shape, xPosition, and the action, which is called by slide, 13 | * and is used to change the state 14 | * Note: We should really create the thread in the constructor, but its Runnable needs access to this class's 15 | * running flag. So construct the sprite, then add the Runnable. 16 | */ 17 | public class ThreadSprite extends Sprite implements InitializingBean { 18 | 19 | protected Thread thread; 20 | @Value("${arrow-length}") 21 | protected int arrowLength; 22 | /** 23 | * set to true to have the sprite animate from right to left when failed 24 | */ 25 | private boolean retreating; 26 | 27 | private Condition condition; 28 | private int conditionId; 29 | private static long threadSequenceNumber; 30 | 31 | public Thread getThread() { 32 | return thread; 33 | } 34 | 35 | public void setThread(Thread thread) { 36 | this.thread = thread; 37 | } 38 | 39 | @Value("${pixels-per-y-step}") 40 | protected int height; 41 | 42 | public Thread.State getThreadState() { 43 | Thread.State state = null; 44 | if (thread != null) { 45 | state = thread.getState(); 46 | } 47 | return state; 48 | } 49 | 50 | /** 51 | * Create the thread associated with this runnable, and starts it 52 | */ 53 | public void attachAndStartRunnable(Runnable runnable, boolean platform) { 54 | if(platform){ 55 | thread = new Thread(runnable, String.format("JCA Platform Thread %d", ++threadSequenceNumber)); 56 | thread.start(); 57 | } 58 | else { 59 | thread = Thread.ofVirtual() 60 | .name(String.format("JCA virtual Thread %d", ++threadSequenceNumber)) 61 | .start(runnable); 62 | } 63 | } 64 | 65 | public void setNextXPosition() { 66 | getState().advancePosition(this); 67 | } 68 | 69 | @Override 70 | public void render(Graphics2D graphics) { 71 | Color color = getThreadContext().getColor(this); 72 | graphics.setColor(color); 73 | // for debugging position and state issues, uncomment this 74 | // println(this); 75 | graphics.drawLine(getXPosition() - arrowLength + getXOffset(), getYPosition(), getXPosition(), getYPosition()); 76 | drawThreadCap(graphics); 77 | renderMessage(graphics); 78 | renderLabel(graphics); 79 | } 80 | 81 | @Override 82 | public void renderMessage(Graphics2D graphics) { 83 | if (thread != null && getThreadContext().isDisplayThreadNames()) { 84 | setMessage(thread.getName()); 85 | } 86 | super.renderMessage(graphics); 87 | } 88 | 89 | /** 90 | * Draws the ball (or whatever) at the end of the thread line 91 | */ 92 | protected void drawThreadCap(Graphics2D graphics) { 93 | graphics.setColor(getThreadContext().getColorByInstance(this)); 94 | int offset = isRetreating() && getDirection() == Direction.left ? arrowLength : 0; 95 | graphics.fillOval(getXPosition() + getXOffset() - 8 - offset, getYPosition() - 5, 10, 10); 96 | } 97 | 98 | /** 99 | * Returns our internal thread state, reflecting the native thread state, with some adjustments (new and runnable 100 | * are both considered runnable, and waiting and timed-waiting are both considered waiting. 101 | */ 102 | public ThreadState getState() { 103 | if (thread == null) { 104 | return null; 105 | } 106 | if (isRetreating()) { 107 | return getThreadContext().retreating; 108 | } 109 | if (this instanceof PooledThreadSprite) { 110 | return getThreadContext().pooled; 111 | } 112 | switch (thread.getState()) { 113 | case NEW: 114 | case RUNNABLE: 115 | return getThreadContext().runnable; 116 | case WAITING: 117 | case TIMED_WAITING: 118 | return getThreadContext().waiting; 119 | case BLOCKED: 120 | return getThreadContext().blocked; 121 | case TERMINATED: 122 | return getThreadContext().terminated; 123 | default: 124 | throw new IllegalArgumentException("Unknown thread state " + thread.getState()); 125 | } 126 | } 127 | 128 | @Override 129 | public void afterPropertiesSet() { 130 | setYPosition(getNextYPositionFromContext()); 131 | } 132 | 133 | protected int getNextYPositionFromContext() { 134 | return getThreadContext().getNextYPosition(height); 135 | } 136 | 137 | @Override 138 | public String toString() { 139 | return getClass().getSimpleName() + 140 | "{" + 141 | "ID=" + getID() + 142 | ", state=" + getState() + 143 | ", native-state=" + (thread != null?thread.getState().toString():"") + 144 | ", interrupt:" + (thread != null?thread.isInterrupted():"") + 145 | // ", x-position=" + getXPosition() + 146 | ", y-position=" + getYPosition() + 147 | ", relative_position=" + getRelativePosition() + 148 | ", " + super.toString() + 149 | '}'; 150 | } 151 | 152 | public boolean isRetreating() { 153 | return retreating; 154 | } 155 | 156 | /** 157 | * sets the spite to be retreating 158 | */ 159 | public void setRetreating() { 160 | this.retreating = true; 161 | } 162 | 163 | public boolean hasCondition() { 164 | return condition != null; 165 | } 166 | 167 | /** 168 | * ConditionId is used by the animation renderer to draw the "C" 169 | */ 170 | public void setCondition(Condition condition, int conditionId) { 171 | this.condition = condition; 172 | setConditionId(conditionId); 173 | } 174 | 175 | public Condition getCondition() { 176 | return condition; 177 | } 178 | 179 | public int getConditionId() { 180 | return conditionId; 181 | } 182 | 183 | public void setConditionId(int conditionId) { 184 | this.conditionId = conditionId; 185 | } 186 | 187 | public void setStopped() { 188 | 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/VirtualRunnerThreadSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.util.ThreadUtils; 4 | import com.vgrazi.jca.util.UIUtils; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | import java.awt.*; 8 | import java.util.Map; 9 | 10 | /** 11 | * This is a regular thread sprite, except that when it is running (inside the monolith), it renders as a round rectangle 12 | * instead of as a thread 13 | */ 14 | public class VirtualRunnerThreadSprite extends RunnerThreadSprite { 15 | 16 | public static final Font CARRIER_FONT = new Font("Arial", Font.PLAIN, 24); 17 | 18 | // String lastCarrier; 19 | @Override 20 | public void render(Graphics2D graphics) { 21 | // Render a bounding box around where the rounded circle should be. 22 | // Graphics2D graphicsDebug = (Graphics2D) graphics.create(); 23 | // graphicsDebug.setStroke(new BasicStroke(1)); 24 | // graphicsDebug.setColor(Color.yellow); 25 | // graphicsDebug.drawRect(leftBound, topBound, width, runnerEllipseHeight); 26 | 27 | Color color = getThreadContext().getColor(this); 28 | // Color color = getColorByThreadState(); 29 | graphics.setColor(color); 30 | if(isInMonolith()){ 31 | graphics.drawArc(-100+leftBound + getXOffset(), topBound, ellipseRadius * 2, ellipseRadius * 2, 90, 180); 32 | graphics.drawArc(-100+rightBound + getXOffset()- ellipseRadius * 2, topBound, ellipseRadius * 2, ellipseRadius * 2, 270, 180); 33 | graphics.drawLine(-100+lineStart + getXOffset(), topBound, -100 + lineEnd + getXOffset(), topBound); 34 | graphics.drawLine(-100+lineStart + getXOffset(), bottomBound, -100 + lineEnd + getXOffset(), bottomBound); 35 | String carrier = ThreadUtils.getCarrier(this.getThread()); 36 | if(carrier != null) { 37 | renderCarrier(graphics, rightBound, yPosition+height/2-5); 38 | Graphics graphics1 = graphics.create(); 39 | graphics1.setFont(new Font("Arial", Font.PLAIN, 24)); 40 | // graphics.drawString(carrier, lineEnd + getXOffset()+15, bottomBound ); 41 | } 42 | }else{// && relativePosition != RelativePosition.At) { 43 | // render the runner thread before it enters the monolith 44 | int xPosition = getXPosition(); 45 | int yPosition = getYPosition(); 46 | graphics.drawLine(-100+xPosition - arrowLength + getXOffset(), yPosition, xPosition + getXOffset(), yPosition); 47 | renderCarrier(graphics, -100+xPosition, yPosition); 48 | } 49 | 50 | renderMessage(graphics); 51 | drawThreadCap(graphics); 52 | renderInterruptedFlag(graphics); 53 | } 54 | 55 | @Override 56 | protected void drawHead(Graphics2D graphics, int capOffset, int offset, int yPos) { 57 | Graphics graphics1 = graphics.create(); 58 | graphics1.setFont(CARRIER_FONT); 59 | int xpos = 0; 60 | if(isInMonolith()) { 61 | xpos = getXPosition() + getXOffset() - 8 - offset + capOffset; 62 | yPos = yPos + 10; 63 | } 64 | else { 65 | xpos = getXPosition() + getXOffset() - offset + capOffset + 5; 66 | yPos += 18; 67 | 68 | } 69 | graphics1.drawString("v", -100+xpos, yPos); 70 | graphics1.dispose(); 71 | } 72 | 73 | private void renderCarrier(Graphics2D graphics, int xPosition, int yPosition) { 74 | Map.Entry entry = ThreadUtils.getVirtualToCarrierMapping(getThread()); 75 | // String virtual = entry.getKey(); 76 | String carrier = entry.getValue(); 77 | 78 | // carrier = carrier.replaceAll("ForkJoinPool-\\d+-", ""); 79 | if(carrier.contains("ForkJoinPool")) { 80 | Graphics graphics1 = graphics.create(); 81 | Color carrierColor = getThreadContext().getCarrierColor(carrier, this); 82 | graphics1.setColor(carrierColor); 83 | int xPos = -100 + (isInMonolith() ? xPosition - arrowLength - 10 : xPosition - arrowLength - 30); 84 | graphics1.fill3DRect(xPos, yPosition + 3, arrowLength + 20, 12, true); 85 | // carrier = carrier.replaceAll("ForkJoinPool-\\d+-", ""); 86 | graphics1.setFont(CARRIER_FONT); 87 | graphics1.drawString(carrier, lineEnd + getXOffset() + 25-100, yPosition + 3 + 12 + 2); 88 | 89 | graphics1.dispose(); 90 | } 91 | } 92 | 93 | 94 | // leftBound ___________________________________ rightBound 95 | // | . |lineStart ^ lineEnd| . | 96 | // | . | | | . | 97 | // | . | | | . | 98 | // |. _______|________________ | ______________|________ .| 99 | // |. | | | .| 100 | // | . | | | . | 101 | // | . | | | . | 102 | // | .| _________________V_______________|__ . | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/sprites/WriteThreadSprite.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.sprites; 2 | 3 | import com.vgrazi.jca.util.RenderUtils; 4 | import org.springframework.beans.factory.InitializingBean; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | import java.awt.*; 8 | 9 | public class WriteThreadSprite extends RunnerThreadSprite implements InitializingBean { 10 | 11 | @Autowired 12 | private Stroke writerStroke; 13 | @Autowired 14 | private Stroke writerHeadStroke; 15 | 16 | @Override 17 | public void render(Graphics2D graphics) { 18 | super.render(graphics); 19 | } 20 | 21 | @Override 22 | protected void drawThreadCap(Graphics2D g) { 23 | Graphics2D graphics = (Graphics2D) g.create(); 24 | int yPos = RenderUtils.getCapYPosition(leftBound, rightBound, topBound, bottomBound, ballDiameter, this) + 5; 25 | int offset = isRetreating() && getDirection() == Direction.left ? arrowLength:0; 26 | int x = getXPosition() -offset; 27 | 28 | // docstore.mik.ua/orelly/java-ent/jfc/ch04_05.htm 29 | graphics.setStroke(writerHeadStroke); 30 | graphics.setColor(Color.yellow); 31 | Polygon polygon; 32 | if(getDirection() == Direction.right) { 33 | polygon = new Polygon( 34 | new int[]{x - 11, x + 1, x - 11}, 35 | new int[]{yPos - 6, yPos, yPos + 6}, 36 | 3); 37 | } 38 | else { 39 | polygon = new Polygon( 40 | new int[]{x + 1, x - 11, x + 1}, 41 | new int[]{yPos - 6, yPos, yPos + 6}, 42 | 3); 43 | } 44 | graphics.fillPolygon(polygon); 45 | graphics.setColor(Color.red); 46 | graphics.drawPolygon(polygon); 47 | graphics.dispose(); 48 | } 49 | 50 | @Override 51 | public void afterPropertiesSet() { 52 | super.afterPropertiesSet(); 53 | setStroke(writerStroke); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Blocked.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.sprites.Sprite; 5 | 6 | public class Blocked extends ThreadState { 7 | 8 | @Override 9 | public void advancePosition(Sprite sprite) { 10 | int position = sprite.getXPosition(); 11 | RelativePosition relativePosition = sprite.getRelativePosition(); 12 | switch (relativePosition) { 13 | case Before: 14 | position += threadContext.pixelsPerStep; 15 | if(position > threadContext.monolithLeftBorder) { 16 | position = threadContext.monolithLeftBorder; 17 | } 18 | sprite.setXPosition(position); 19 | break; 20 | case At: 21 | case After: 22 | case In: 23 | break; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Getting.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.sprites.Sprite; 4 | import com.vgrazi.jca.context.ThreadContext; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | 8 | public class Getting extends ThreadState { 9 | @Autowired 10 | ThreadContext threadContext; 11 | 12 | @Value("${monolith-right-border}") 13 | private int leftBorder; 14 | 15 | @Value("${arrow-length}") 16 | private int arrowLength; 17 | @Override 18 | public void advancePosition(Sprite sprite) { 19 | sprite.setXPosition(leftBorder + arrowLength); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Pooled.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.context.ThreadContext; 5 | import com.vgrazi.jca.sprites.Sprite; 6 | import com.vgrazi.jca.sprites.ThreadSprite; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class Pooled extends ThreadState { 12 | @Autowired 13 | ThreadContext threadContext; 14 | @Override 15 | public void advancePosition(Sprite sprite) { 16 | ThreadSprite threadSprite = (ThreadSprite) sprite; 17 | RelativePosition relativePosition = threadSprite.getRelativePosition(); 18 | switch (relativePosition) { 19 | case Before: 20 | if (threadSprite.getDirection() == Sprite.Direction.right) { 21 | threadSprite.setXPosition(monolithLeftBorder + 10); 22 | } 23 | else { 24 | calculatePreviousPosition(threadSprite); 25 | } 26 | break; 27 | case At: 28 | case In: 29 | calculateNextPositionIn(sprite); 30 | break; 31 | case After: 32 | calculateNextPositionAfter(sprite); 33 | break; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Retreating.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.context.ThreadContext; 5 | import com.vgrazi.jca.sprites.Sprite; 6 | import com.vgrazi.jca.sprites.ThreadSprite; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | public class Retreating extends ThreadState { 10 | @Autowired 11 | ThreadContext threadContext; 12 | 13 | @Override 14 | public void advancePosition(Sprite sprite) { 15 | ThreadSprite threadSprite = (ThreadSprite) sprite; 16 | RelativePosition relativePosition = threadSprite.getRelativePosition(); 17 | switch (relativePosition) { 18 | case Before: 19 | if (threadSprite.getDirection() != Sprite.Direction.left) { 20 | calculateNextPositionBefore(threadSprite); 21 | } 22 | else { 23 | calculatePreviousPosition(threadSprite); 24 | } 25 | break; 26 | case At: 27 | case In: 28 | case After: 29 | threadSprite.setDirection(Sprite.Direction.left); 30 | calculatePreviousPosition(threadSprite); 31 | break; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Running.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.sprites.Sprite; 5 | import com.vgrazi.jca.context.ThreadContext; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | public class Running extends ThreadState { 9 | @Autowired 10 | ThreadContext threadContext; 11 | 12 | @Override 13 | public void advancePosition(Sprite sprite) { 14 | RelativePosition relativePosition = sprite.getRelativePosition(); 15 | // println(sprite + " " + relativePosition); 16 | switch (relativePosition) { 17 | case Before: 18 | calculateNextPositionBefore(sprite); 19 | break; 20 | case At: 21 | case In: 22 | calculateNextPositionIn(sprite); 23 | break; 24 | case After: 25 | calculateNextPositionAfter(sprite); 26 | break; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/State.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.sprites.Sprite; 4 | 5 | public interface State { 6 | /** 7 | * Based on the current state, calculates the next position of the sprite 8 | * For example, for Threads, the state is automatically returned by ThreadSprite.getState(), based on the sprite's state 9 | */ 10 | void advancePosition(Sprite sprite); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Terminated.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.ThreadContext; 4 | import com.vgrazi.jca.sprites.ObjectSprite; 5 | import com.vgrazi.jca.sprites.Sprite; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | 9 | public class Terminated extends ThreadState { 10 | @Autowired 11 | private ThreadContext threadContext; 12 | 13 | @Value("${monolith-right-border}") 14 | private int monolithRightBorder; 15 | 16 | @Override 17 | public void advancePosition(Sprite sprite) { 18 | // if the thread is terminated, we want to get it out of 19 | // the monolith as fast as possible, so it does not 20 | // appear to be occupying the lock at the same time as some other 21 | // runnable thread 22 | int xPosition = sprite.getXPosition(); 23 | if (!(sprite instanceof ObjectSprite)) { 24 | if(xPosition < monolithRightBorder) { 25 | xPosition = monolithRightBorder; 26 | } 27 | } 28 | // Some sprites, eg ReadWriteLock the direction is important - we don't want the arrow point left, when the sprite is moving right! 29 | if(sprite.getDirection() == Sprite.Direction.left) { 30 | sprite.setDirection(Sprite.Direction.right); 31 | } 32 | sprite.setXPosition(xPosition + threadContext.pixelsPerStep); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/ThreadState.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.ThreadContext; 4 | import com.vgrazi.jca.sprites.Sprite; 5 | import com.vgrazi.jca.sprites.ThreadSprite; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * Represents all of the supported thread states, and contains the algorithms for calculating the next position, 12 | * depending on the current position. For example, the Runnable state will check the position, and if to the left of the 13 | * monolith, will advance the thread to the right 14 | */ 15 | @Component 16 | public abstract class ThreadState implements State { 17 | 18 | @Autowired 19 | protected ThreadContext threadContext; 20 | @Value("${monolith-left-border}") 21 | public int monolithLeftBorder; 22 | @Value("${monolith-right-border}") 23 | public int monolithRightBorder; 24 | 25 | @Value("${arrow-length}") 26 | private int arrowLength; 27 | 28 | @Override 29 | public String toString() { 30 | return getClass().getSimpleName(); 31 | } 32 | 33 | /** 34 | * Given a sprite before the monolith, calculates the next position 35 | * and stores it in the sprite 36 | */ 37 | void calculateNextPositionBefore(Sprite sprite) { 38 | int position = sprite.getXPosition(); 39 | position += threadContext.pixelsPerStep; 40 | if (position > monolithLeftBorder) { 41 | position = monolithLeftBorder; 42 | } 43 | sprite.setXPosition(position); 44 | } 45 | 46 | void calculatePreviousPosition(ThreadSprite sprite) { 47 | int position = sprite.getXPosition(); 48 | position -= threadContext.pixelsPerStep; 49 | sprite.setXPosition(position); 50 | if (position < 0) { 51 | threadContext.stopThread(sprite); 52 | } 53 | } 54 | 55 | /** 56 | * Given a sprite inside the monolith, calculates the next position 57 | * and stores it in the sprite 58 | */ 59 | void calculateNextPositionIn(Sprite sprite) { 60 | int position = sprite.getXPosition(); 61 | ThreadSprite.Direction direction = sprite.getDirection(); 62 | switch (direction) { 63 | case right: 64 | position += threadContext.pixelsPerStepRunner; 65 | if (position > monolithRightBorder -1) { 66 | position = monolithRightBorder -1; 67 | // todo: build the rotational animation here 68 | sprite.setXPosition(position); 69 | sprite.setDirection(ThreadSprite.Direction.left); 70 | } else { 71 | sprite.setXPosition(position); 72 | } 73 | break; 74 | case left: 75 | position -= threadContext.pixelsPerStepRunner; 76 | if (position < monolithLeftBorder + 1) { 77 | position = monolithLeftBorder + 1; 78 | // todo: build the rotational animation here 79 | sprite.setXPosition(position); 80 | sprite.setDirection(ThreadSprite.Direction.right); 81 | } else { 82 | sprite.setXPosition(position); 83 | } 84 | break; 85 | } 86 | } 87 | 88 | void calculateNextPositionAfter(Sprite sprite) { 89 | sprite.setXPosition(sprite.getXPosition() + threadContext.pixelsPerStep); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/states/Waiting.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.states; 2 | 3 | import com.vgrazi.jca.context.RelativePosition; 4 | import com.vgrazi.jca.sprites.Sprite; 5 | import com.vgrazi.jca.context.ThreadContext; 6 | import org.springframework.beans.factory.InitializingBean; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | 10 | public class Waiting extends ThreadState implements InitializingBean { 11 | @Autowired 12 | ThreadContext threadContext; 13 | @Value("${monolith-left-border}") 14 | private int monolithLeftBorder; 15 | @Value("${monolith-right-border}") 16 | private int monolithRightBorder; 17 | @Value("${arrow-length}") 18 | private int arrowLength; 19 | 20 | private int waitingXPos; 21 | 22 | @Override 23 | public void advancePosition(Sprite sprite) { 24 | RelativePosition relativePosition = sprite.getRelativePosition(); 25 | switch (relativePosition) { 26 | case Before: 27 | calculateNextPositionBefore(sprite); 28 | break; 29 | case At: 30 | case After: 31 | // nothing to do, this thread is waiting 32 | break; 33 | case In: 34 | // if sprite hasn't reached its final resting place, let it keep inching forward. (If it is already retreating to the left, just force it to its resting position) 35 | if (sprite.getDirection() == Sprite.Direction.left || sprite.getXPosition() + arrowLength >= waitingXPos) { 36 | sprite.setXPosition(waitingXPos); 37 | } 38 | else { 39 | calculateNextPositionIn(sprite); 40 | } 41 | break; 42 | } 43 | } 44 | 45 | 46 | @Override 47 | public void afterPropertiesSet() { 48 | waitingXPos = (monolithLeftBorder + monolithRightBorder + arrowLength) / 2; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/HtmlUtils.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import javax.swing.text.BadLocationException; 4 | import javax.swing.text.Element; 5 | import javax.swing.text.html.HTMLDocument; 6 | import java.util.logging.Logger; 7 | 8 | public class HtmlUtils { 9 | private final static String blanks = " "; // using Java 8 :( 10 | 11 | private static Logger logger = Logger.getLogger("HtmlUtils"); 12 | private static void println(String message) { 13 | logger.info(message); 14 | } 15 | /** 16 | * Displays the html structure of the supplied node. 17 | * To start, pass in an HTMLDocument, null, 0 18 | */ 19 | public static void displayHtml(HTMLDocument htmlDocument, Element node, int level) { 20 | try { 21 | String padding = blanks.substring(0, level * 2); 22 | if (node == null) { 23 | println("==============="); 24 | node = htmlDocument.getRootElements()[0]; 25 | 26 | } 27 | println("****" + padding + node.getName()); 28 | if (node.isLeaf()) { 29 | println("***-" + padding + " " + htmlDocument.getText(node.getStartOffset(), node.getEndOffset() - node.getStartOffset())); 30 | } else { 31 | int count = node.getElementCount(); 32 | for (int i = 0; i < count; i++) { 33 | Element element = node.getElement(i); 34 | displayHtml(htmlDocument, element, level + 1); 35 | } 36 | } 37 | } catch (BadLocationException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/IDGenerator.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | /** 6 | * Generates unique IDs beginning from 1 7 | */ 8 | public class IDGenerator { 9 | private static AtomicInteger ID = new AtomicInteger(1); 10 | public static int next() { 11 | return ID.getAndIncrement(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/Logging.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import com.vgrazi.jca.sprites.Sprite; 4 | import com.vgrazi.jca.sprites.ThreadSprite; 5 | 6 | import java.time.LocalTime; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.logging.Logger; 10 | 11 | public class Logging { 12 | static Map messageCache = new HashMap<>(); 13 | static private long stepDelay = 2000; 14 | static Logger logger = Logger.getLogger("Logging"); 15 | 16 | public static void logAndSleep(long delay, String message) throws InterruptedException { 17 | Thread.sleep(delay); 18 | println(message); 19 | } 20 | 21 | public static void logAndSleep(String message, ThreadSprite sprite) { 22 | logAndSleep(stepDelay, message, sprite); 23 | } 24 | 25 | public static void logAndSleep(String message) throws InterruptedException { 26 | logAndSleep(stepDelay, message); 27 | } 28 | 29 | public static void logAndSleep(long stepDelay, String message, ThreadSprite sprite) { 30 | try { 31 | message(message, sprite); 32 | Thread.sleep(stepDelay); 33 | } catch (InterruptedException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | public static void log(String message, Sprite sprite) { 39 | message(message, sprite); 40 | } 41 | 42 | private static void message(String message, Sprite sprite) { 43 | String cachedMessage = messageCache.get(sprite); 44 | if (cachedMessage == null || !cachedMessage.equals(message)) { 45 | messageCache.put(sprite, message); 46 | String newMessage = message + " " + sprite; 47 | log(newMessage); 48 | } 49 | } 50 | 51 | public static void log(Sprite sprite) { 52 | String message = sprite.toString(); 53 | String cachedMessage = messageCache.get(sprite); 54 | if (cachedMessage == null || !cachedMessage.equals(message)) { 55 | messageCache.put(sprite, message); 56 | println(message); 57 | } 58 | } 59 | 60 | public static void log(String message) { 61 | println(message); 62 | } 63 | 64 | public static void println(String message) { 65 | logger.info(message); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/Parsers.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import java.awt.*; 4 | 5 | public class Parsers { 6 | /** 7 | * Given a comma separated String of decimal integers, converts to a color 8 | */ 9 | public static Color parseColor(String colorString) { 10 | String[] split = colorString.split("[,;]"); 11 | Color color = new Color(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2])); 12 | return color; 13 | } 14 | 15 | /** 16 | * Parses a font of the form Name;Type;Size 17 | * (Must not contain spaces. For Bold Italic use "bold-italic") 18 | * @param fontDescriptor 19 | * @return 20 | */ 21 | public static Font parseFont(String fontDescriptor) { 22 | String[] split = fontDescriptor.split("[;,]"); 23 | Font font = new Font(split[0], parseFontStyle(split[1]), Integer.parseInt(split[2])); 24 | return font; 25 | } 26 | 27 | public static int parseFontStyle(String styleName) { 28 | int styleCode; 29 | String lcStyle = styleName.toLowerCase(); 30 | switch (lcStyle) { 31 | case "plain": 32 | styleCode = Font.PLAIN; 33 | break; 34 | case "bold": 35 | styleCode = Font.BOLD; 36 | break; 37 | case "italic": 38 | styleCode = Font.ITALIC; 39 | break; 40 | case "bold-italic": 41 | styleCode = Font.BOLD+Font.ITALIC; 42 | break; 43 | default: 44 | throw new IllegalArgumentException("Unknown Font Style " + styleName); 45 | } 46 | return styleCode; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/RenderUtils.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import com.vgrazi.jca.sprites.Sprite; 4 | 5 | public class RenderUtils { 6 | /** 7 | * Renders the ball at the correct position 8 | * @param leftBound the left-most position of the ellipse 9 | * @param rightBound the right-most position of the ellipse 10 | * @param topBound the y position of the top-most position of the ellipse 11 | * @param bottomBound the y position of the bottom-most position of the ellipse 12 | * @param ballDiameter the diameter of the actual cap 13 | * @param sprite the sprite we are animating 14 | */ 15 | public static int getCapYPosition(int leftBound, int rightBound, int topBound, int bottomBound, int ballDiameter, Sprite sprite) { 16 | int xPos = sprite.getXPosition(); 17 | // note that the "ellipse" is really just 2 semi-circles connected by straight horizontal lines, 18 | // and the radius os 1/2 the height 19 | int ellipseRadius = (bottomBound - topBound) / 2; 20 | int xAxis = (bottomBound + topBound) / 2; 21 | 22 | // this is the x-position where the left semi-circle stops and the straight horizontal line start: 23 | int lineStart = leftBound + ellipseRadius; 24 | // this is the x-position where the straight horizontal line ends and the right semi-circle starts" 25 | int lineEnd = rightBound - ellipseRadius; 26 | int yPos; 27 | switch (sprite.getDirection()) { 28 | case right: 29 | if (xPos <= leftBound) { 30 | // we have not entered the left semicircle yet 31 | yPos = xAxis; 32 | } else if (xPos < lineStart) { 33 | // we are in the left top semicircle 34 | int legLength = ellipseRadius - (xPos - leftBound); 35 | yPos = xAxis - (int) Math.sqrt(ellipseRadius * ellipseRadius - legLength * legLength); 36 | } else if (xPos < lineEnd) { 37 | // we are in the top line 38 | yPos = topBound; 39 | } else { 40 | // we are in the right semi circle 41 | int legLength = xPos - lineEnd; 42 | yPos = xAxis - (int) Math.sqrt(ellipseRadius * ellipseRadius - legLength * legLength); 43 | } 44 | break; 45 | case left: 46 | if (xPos <= lineStart) { 47 | // we are in the left bottom semicircle 48 | int legLength = ellipseRadius - (xPos - leftBound); 49 | yPos = xAxis + (int) Math.sqrt(ellipseRadius * ellipseRadius - legLength * legLength); 50 | } else if (xPos < lineEnd) { 51 | // we are in the bottom line 52 | yPos = bottomBound; 53 | } else { 54 | // we are in the right bottom semicircle 55 | int legLength = xPos - lineEnd; 56 | yPos = xAxis + (int) Math.sqrt(ellipseRadius * ellipseRadius - legLength * legLength); 57 | } 58 | break; 59 | default: 60 | throw new IllegalArgumentException("Unknown direction-should never happen - did you add a direction besides left and right??"); 61 | } 62 | yPos -= ballDiameter / 2; 63 | // System.out.printf("old x-pos:%d new xPos:%d line-start:%d ypos:%d ellipse radius:%d xaxis:%d ball-diameter:%d%n", 64 | // this.xPosition, xPos + ballDiameter/2, lineStart, yPos + ballDiameter /2, ellipseRadius, xAxis, ballDiameter); 65 | return yPos; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import io.micrometer.core.lang.Nullable; 4 | 5 | public class StringUtils { 6 | public static boolean isBlank(@Nullable String string) { 7 | if (isEmpty(string)) { 8 | return true; 9 | } else { 10 | for(int i = 0; i < string.length(); ++i) { 11 | if (!Character.isWhitespace(string.charAt(i))) { 12 | return false; 13 | } 14 | } 15 | 16 | return true; 17 | } 18 | } 19 | 20 | public static boolean isEmpty(@Nullable String string) { 21 | return string == null || string.isEmpty(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/ThreadUtils.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import java.util.Map; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | public class ThreadUtils { 8 | private final static Pattern virtualNameMappingPattern = Pattern.compile("(.+])"); 9 | private final static Pattern carrierNameMappingPattern = Pattern.compile(".*@(.+)"); 10 | private final static Pattern noCarrierMappingPattern = Pattern.compile("(.+])/.*"); 11 | public static Map.Entry getVirtualToCarrierMapping(Thread thread) { 12 | String name = thread.toString(); 13 | Matcher matcher = virtualNameMappingPattern.matcher(name); 14 | String virtualName = ""; 15 | if(matcher.find()) { 16 | virtualName = matcher.group(1); 17 | } 18 | String carrier; 19 | matcher = carrierNameMappingPattern.matcher(name); 20 | if(matcher.find()) { 21 | carrier = matcher.group(1); 22 | } 23 | else { 24 | carrier = ""; 25 | } 26 | return Map.entry(virtualName, carrier); 27 | } 28 | 29 | public static String getCarrier(Thread thread){ 30 | String threadReference = thread.toString(); 31 | String carrier; 32 | Matcher matcher = carrierNameMappingPattern.matcher(threadReference); 33 | if(matcher.find()) { 34 | carrier = matcher.group(1); 35 | } 36 | else { 37 | carrier = ""; 38 | } 39 | 40 | return carrier; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/util/UIUtils.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.util; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.awt.image.BufferedImage; 9 | import java.net.URL; 10 | import java.util.logging.Logger; 11 | 12 | @Component 13 | public class UIUtils { 14 | private static Logger logger = Logger.getLogger("UIUtils"); 15 | private static void println(String message) { 16 | logger.info(message); 17 | } 18 | 19 | @Autowired 20 | private JPanel cardPanel; 21 | 22 | public void setImage(String imageName, JLabel jlabel, float scaling) { 23 | ImageIcon imageIcon = getImageIcon(imageName); 24 | jlabel.setIcon(imageIcon); 25 | Image image = imageIcon.getImage(); 26 | int width = (int) (jlabel.getWidth()*.7); 27 | int height = (int) (jlabel.getHeight()*.7); 28 | Image scaledImage = getScaledImage(image, width, height, scaling); 29 | if (scaledImage != null) { 30 | imageIcon.setImage(scaledImage); 31 | jlabel.setIcon(imageIcon); 32 | } 33 | } 34 | 35 | public ImageIcon getImageIcon(String imageName) { 36 | try { 37 | URL url = getClass().getClassLoader().getResource(imageName); 38 | ImageIcon imageIcon = new ImageIcon(url); 39 | return imageIcon; 40 | } catch (RuntimeException e) { 41 | println("UIUtils.getImageIcon Can't find image at " + imageName); 42 | throw e; 43 | } 44 | } 45 | public Image getImage(String imageName) { 46 | try { 47 | ImageIcon imageIcon = getImageIcon(imageName); 48 | return imageIcon.getImage(); 49 | } catch (RuntimeException e) { 50 | println("UIUtils.getImageIcon Can't find image at " + imageName); 51 | throw e; 52 | } 53 | } 54 | 55 | private Image getScaledImage(Image srcImg, int width, int height, float scaling) { 56 | if(width <= 0 || height <= 0) { 57 | return null; 58 | } 59 | int imageWidth = srcImg.getWidth(null); 60 | int imageHeight = srcImg.getHeight((img, infoflags, x, y, width1, height1) -> false); 61 | double aspectRatio = imageWidth / (double) imageHeight; 62 | double labelRatio = cardPanel.getWidth() / (double) cardPanel.getHeight(); 63 | if(aspectRatio < labelRatio) { 64 | // use the label width: label_width/image-height = aspectRatio 65 | width = cardPanel.getWidth(); 66 | height = (int) (width/aspectRatio); 67 | } 68 | else { 69 | // use the label height: image-width/label_height = aspectRatio 70 | height = cardPanel.getHeight(); 71 | width = (int) (aspectRatio * height); 72 | } 73 | BufferedImage resizedImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 74 | Graphics2D g2 = resizedImg.createGraphics(); 75 | 76 | g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 77 | g2.setColor(Color.black); 78 | g2.fillRect(0,0, 5000,5000); 79 | g2.drawImage(srcImg, (int) (width * (1-scaling)/2), (int) (height*(1-scaling)/2), (int) (width*scaling), (int) (height*scaling), null); 80 | g2.dispose(); 81 | 82 | return resizedImg; 83 | } 84 | 85 | public static Color applyAlpha(Color color, int alpha) { 86 | return new Color(color.getRed(), color.getBlue(), color.getGreen(), alpha); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/view/ButtonLayout.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.view; 2 | 3 | import java.awt.*; 4 | import java.util.Arrays; 5 | 6 | /** 7 | * Lays out buttons all same width and height, starting from the top of the target container, allowing some gap 8 | */ 9 | public class ButtonLayout extends FlowLayout { 10 | private int vgap; 11 | 12 | public ButtonLayout(int vgap) { 13 | super(); 14 | this.vgap = vgap; 15 | } 16 | 17 | @Override 18 | public void layoutContainer(Container target) { 19 | int y = 2; 20 | int width = Arrays.stream(target.getComponents()).mapToInt(comp -> comp.getPreferredSize().width).max().orElse(20); 21 | for (Component component : target.getComponents()) { 22 | if(component instanceof ControlPanel) { 23 | layoutControlPanel((ControlPanel)component, target, width); 24 | } 25 | else { 26 | int height = component.getPreferredSize().height; 27 | component.setBounds(2, y, width, height); 28 | y += height + vgap; 29 | } 30 | } 31 | } 32 | 33 | private void layoutControlPanel(ControlPanel controlPanel, Container target, int width) { 34 | int height = controlPanel.getPreferredSize().height; 35 | int y = target.getHeight() - height - 2; 36 | controlPanel.setBounds(2, y, width, height); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/view/ButtonPanelLayout.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.view; 2 | 3 | import java.awt.*; 4 | 5 | /** 6 | * Lays out the buttons in proper FlowLayout fashion, one after another until the end of line is reached, then continuing on the next line 7 | */ 8 | public class ButtonPanelLayout extends FlowLayout { 9 | public ButtonPanelLayout(int hgap, int vgap) { 10 | super(LEFT, hgap, vgap); 11 | } 12 | 13 | @Override 14 | public void layoutContainer(Container target) { 15 | int containerWidth = target.getWidth(); 16 | int height = 0; 17 | int delta = 0; 18 | int xPos = 0; 19 | int yPos = 0; 20 | for (Component component : target.getComponents()) { 21 | if(height == 0) { 22 | height = delta = (int)component.getPreferredSize().getHeight(); 23 | } 24 | int componentHeight = (int)component.getPreferredSize().getHeight(); 25 | int componentWidth = (int)component.getPreferredSize().getWidth(); 26 | if(componentWidth + xPos > containerWidth - getHgap()) { 27 | yPos += delta + getVgap(); 28 | xPos = getHgap(); 29 | } 30 | component.setBounds(xPos, yPos, componentWidth, componentHeight); 31 | xPos += componentWidth + getHgap(); 32 | } 33 | int newHeight = yPos + delta; 34 | if(newHeight== 0) { 35 | newHeight = 26; 36 | } 37 | // println("Setting bounds to " + target.getWidth() + "," + newHeight); 38 | target.setSize(target.getWidth(), newHeight); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/view/ControlPanel.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.view; 2 | 3 | import com.vgrazi.jca.context.ThreadContext; 4 | import com.vgrazi.jca.slides.Slide; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.swing.*; 9 | import java.awt.*; 10 | import java.awt.event.ActionListener; 11 | 12 | @Component 13 | public class ControlPanel extends JPanel { 14 | @Autowired 15 | private ThreadContext context; 16 | 17 | static int currentFontSize = 18; 18 | @Autowired 19 | public ControlPanel() { 20 | super(new GridLayout(2, 3)); 21 | JButton aA = new JButton("Aa") { 22 | @Override 23 | public void setToolTipText(String text) { 24 | super.setToolTipText(text); 25 | } 26 | }; 27 | aA.setToolTipText("Current font size:" + 20); 28 | addButton(aA, l1 -> { 29 | Slide slide = context.getSlide(); 30 | currentFontSize +=2; 31 | if(currentFontSize > 24) { 32 | currentFontSize = 14; 33 | } 34 | aA.setToolTipText("Current font size:" + currentFontSize); 35 | slide.setSnippetFontSize(currentFontSize); 36 | }, "Current font size:" + currentFontSize); 37 | addButton(new JButton("||"), l -> context.setSpeed("pause"), "Pause"); 38 | addButton(new JButton(">"), l -> context.setSpeed("slow"), "Slow"); 39 | addButton(new JButton(">>"),l -> context.setSpeed("normal"), "Normal"); 40 | addButton(new JButton("T"), l -> context.setDisplayThreadNames(!context.isDisplayThreadNames()), "Display Thread Names"); 41 | addButton(new JButton(":)"),l -> context.toggleGraphics(), "Toggle Graphics"); 42 | } 43 | 44 | private void addButton(JButton button, ActionListener l, String toolTipText) { 45 | add(button); 46 | button.addActionListener(l); 47 | button.setToolTipText(toolTipText); 48 | } 49 | 50 | public float getFontSize() { 51 | return currentFontSize; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/vgrazi/jca/view/SnippetCanvas.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.jca.view; 2 | 3 | import org.springframework.beans.factory.InitializingBean; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.swing.*; 8 | import javax.swing.text.BadLocationException; 9 | import javax.swing.text.Element; 10 | import javax.swing.text.html.HTMLDocument; 11 | import javax.swing.text.html.HTMLEditorKit; 12 | import javax.swing.text.html.StyleSheet; 13 | import java.awt.*; 14 | import java.io.IOException; 15 | 16 | @Component 17 | public class SnippetCanvas extends JEditorPane { 18 | @Value("${snippet-font-family}") 19 | private String fontFamily; 20 | 21 | @Value("${snippet-font-style}") 22 | private String fontStyle; 23 | 24 | private StyleSheet styleSheet; 25 | 26 | private Element htmlElement; 27 | 28 | public SnippetCanvas() { 29 | setContentType("text/html"); 30 | setOpaque(true); 31 | setBackground(Color.white); 32 | } 33 | 34 | /** 35 | * Sets the font size in px for the "outer" dimv 36 | */ 37 | public void setFontSize(int fontSize) { 38 | // styleSheet.addRule(String.format(".outer{font-style:\"bold\";font-size:%d px;}", fontSize)); 39 | // applyStyles(); 40 | } 41 | 42 | // int counter; 43 | // 44 | // public void changer() { 45 | // ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); 46 | // scheduledExecutorService.scheduleAtFixedRate(this::change, 2000, 500, TimeUnit.MILLISECONDS); 47 | // } 48 | // 49 | // // Diagnostic code: todo: remove 50 | // public void change() { 51 | // println("Counter:" + counter); 52 | // if (counter == 4) { 53 | // println("Replacing"); 54 | // try { 55 | // setSnippet("
" + 56 | // "replaced test 1
" + 57 | // "replaced test 2
" + 58 | // "
" + 59 | // ""); 60 | //// HtmlUtils.displayHtml(htmlDocument, null, 0); 61 | // } catch (BadLocationException e) { 62 | // e.printStackTrace(); 63 | // } catch (IOException e) { 64 | // e.printStackTrace(); 65 | // } 66 | // } else if (counter == 7) { 67 | // removeContent(); 68 | // } 69 | // 70 | // if (counter++ % 2 == 0) { 71 | // styleSheet.addRule(".synchronized {background-color: yellow; color: green;}"); 72 | // } else { 73 | // styleSheet.addRule(".synchronized {background-color: red; color: white;}"); 74 | // } 75 | // reapplyStyles(); 76 | // } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.main.allow-circular-references=true 2 | monolith-left-border=250 3 | monolith-right-border=350 4 | pixels-per-step=25 5 | pixels-per-step-runner=15 6 | pixels-per-y-step=50 7 | completable-future-height=18 8 | initial-y-position=90 9 | ball-diameter=10 10 | runner-ellipse-height=20 11 | stroke-width=3 12 | # the y position of the line separating the top and bottom of the monolith 13 | initial-bottom-y-position=400 14 | arrow-length=70 15 | frame-x=30 16 | frame-y=30 17 | frame-width=1300 18 | frame-height=700 19 | BLOCKED_COLOR=00,180,255 20 | RUNNABLE_COLOR=0,255,0 21 | WAITING_COLOR=255,255,255 22 | TIMED_WAITING_COLOR=92,92,92 23 | TERMINATED_COLOR=255,0,0 24 | DEFAULT_COLOR=255,255,0 25 | UNKNOWN_COLOR=100,100,100 26 | MONOLITH-COLOR=150,150,150 27 | SLIDE-LABEL-COLOR=255,255,255 28 | BOTTOM-LABEL-COLOR=0,0,255 29 | FUTURE-DEFAULT-COLOR=247,118,203 30 | FUTURE-DONE-COLOR=197,68,153 31 | FUTURE-TEXT-COLOR=255,255,255 32 | FUTURE-TEXT-FONT=SansSerif;Bold;20 33 | HTML_DISABLED_COLOR=#808080; 34 | future-width=80 35 | future-height=40 36 | future-top-margin=8 37 | runnable-width=40 38 | runnable-height=15 39 | runnable-top-margin=8 40 | menu-button-vgap=2 41 | BUTTON-PANEL-COLOR=0,0,0 42 | slide-label-font-name=SansSerif 43 | slide-label-font-style=Bold 44 | slide-label-font-size=30 45 | slide-bottom-label-font-name=SansSerif 46 | slide-bottom-label-font-style=Bold 47 | slide-bottom-label-font-size=24 48 | snippet-font-family=Courier New 49 | snippet-font-style=Plain 50 | snippet-font-size=15 51 | snippet-font=SansSerif;PLAIN;18 52 | message-font=SansSerif;Bold;15 53 | selected-font-color-style={color:'black'; font-weight:'bold';} 54 | deselected-font-color-style={color:'gray'; font-weight:'plain';} 55 | thread-message-font=SansSerif;Plain;18 56 | condition-font=SansSerif,Bold,20 57 | condition-color=255,0,0 58 | # the ration between the animation pane and the snippet pane (higher ratio makes animation pane wider and snippet pane narrower 59 | animation-pane-to-snippet-divider-ratio=.475 60 | -------------------------------------------------------------------------------- /src/main/resources/images/AtomicInteger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/AtomicInteger.jpg -------------------------------------------------------------------------------- /src/main/resources/images/SpritesList.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/SpritesList.jpg -------------------------------------------------------------------------------- /src/main/resources/images/VisibilityAndSynchronization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/VisibilityAndSynchronization.png -------------------------------------------------------------------------------- /src/main/resources/images/blockingQueue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/blockingQueue.jpg -------------------------------------------------------------------------------- /src/main/resources/images/completionService.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/completionService.jpg -------------------------------------------------------------------------------- /src/main/resources/images/concurrent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/concurrent.jpg -------------------------------------------------------------------------------- /src/main/resources/images/concurrentText.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/concurrentText.jpg -------------------------------------------------------------------------------- /src/main/resources/images/countdownLatch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/countdownLatch.jpg -------------------------------------------------------------------------------- /src/main/resources/images/cover-slide.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/cover-slide.pptx -------------------------------------------------------------------------------- /src/main/resources/images/cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/cursor.png -------------------------------------------------------------------------------- /src/main/resources/images/cyclicBarrier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/cyclicBarrier.jpg -------------------------------------------------------------------------------- /src/main/resources/images/executors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/executors.jpg -------------------------------------------------------------------------------- /src/main/resources/images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/flag.png -------------------------------------------------------------------------------- /src/main/resources/images/future.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/future.jpg -------------------------------------------------------------------------------- /src/main/resources/images/mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/mesh.png -------------------------------------------------------------------------------- /src/main/resources/images/reentrantLock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/reentrantLock.jpg -------------------------------------------------------------------------------- /src/main/resources/images/reentrantRWLock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/main/resources/images/reentrantRWLock.jpg -------------------------------------------------------------------------------- /src/main/resources/snippets/atomic-integer.html: -------------------------------------------------------------------------------- 1 | <0 comment>// Construct the AtomicVariable, 2 | // assigning an initial value 3 | <0 keyword>final <0 default>AtomicInteger atomicInteger 4 | = <0 keyword>new <0 default>AtomicInteger(<0 literal>1<0 default>); 5 | 6 | <2 comment> 7 | // Arithmetic functions on atomics 8 | // perform their computations in 9 | // an atomic fashion and return 10 | // the result. 11 | <2 keyword>int <2 default>result = atomicInteger 12 | .getAndIncrement(); 13 | 14 | <1 comment>// compareAndSet does an atomic 15 | // "check and set if". 16 | // Value is only set 17 | // if original value == assumed value 18 | <1 keyword>int <1 default>assumedValue = <1 literal>10<1 default>, newValue = <1 literal>5<1 default>; 19 | <1 keyword>boolean <1 default>success = 20 | atomicInteger.compareAndSet( 21 | assumedValue, newValue); 22 | 23 | -------------------------------------------------------------------------------- /src/main/resources/snippets/blocking-queue.html: -------------------------------------------------------------------------------- 1 | <0 comment>// Constructor - pass in the upper bound 2 | <0 keyword>final <0 default>BlockingQueue queue 3 | = <0 keyword>new <0 default>ArrayBlockingQueue(4); 4 | 5 | <1 comment>// Threads attempting to put will block 6 | // until there is room in the buffer 7 | <1 default>Thread putThread = <1 keyword>new <1 default>Thread() { 8 | <1 keyword>public void <1 default>run() { 9 | <1 keyword>try <1 default>{ 10 | queue.put(someObject); 11 | } <1 keyword>catch<1 default>(InterruptedException e) {..}} 12 | 13 | <6 comment>// add is like put 14 | <6 comment>// except throws IllegalStateException (unchecked) if full 15 | <6 default>Thread addingThread = <6 keyword>new <6 default>Thread() { 16 | <6 keyword>public void <6 default>run() { 17 | <6 default> try{ 18 | queue.add(someObject); <6 comment>// ignore boolean return type, always true 19 | <6 default> }<6 keyword>catch<6 default>(IllegalStateException e){..} }} 20 | 21 | <2 comment>// offer is like put except returns false if no room 22 | <2 default>Thread offerThread = <2 keyword>new <2 default>Thread() { 23 | <2 keyword>public void <2 default>run() { 24 | boolean success = queue.offer(someObject); 25 | }} 26 | 27 | <4 comment>// offer(time) is like put except that it times out 28 | // after the specified timeout period and returns fails 29 | <4 default>Thread offerThread = <4 keyword>new <4 default>Thread() { 30 | <4 keyword>public void <4 default>run() { 31 | <4 keyword>try<4 default> { 32 | boolean success = queue.offer(someObject, 1L, 33 | TimeUnit.SECONDS); 34 | } <4 keyword>catch<4 default>(InterruptedException e) { }} 35 | 36 | <5 comment>// Threads attempting to take will block 37 | // until the there is something to take 38 | <5 default>Thread takeThread = <5 keyword>new <5 default>Thread() { 39 | <5 keyword>public void <5 default>run() { 40 | queue.take(); 41 | }} 42 | 43 | <3 comment>// Threads attempting to poll will return 44 | // null if there is nothing on the queue 45 | <3 default>Thread pollThread = <3 keyword>new <3 default>Thread() { 46 | <3 keyword>public void <3 default>run() { 47 | queue.poll(); 48 | }} 49 | 50 | -------------------------------------------------------------------------------- /src/main/resources/snippets/completable-future.html: -------------------------------------------------------------------------------- 1 | <2,1 default>CompletableFuture future = 2 | <2 default> CompletableFuture.supplyAsync(() -> 3 | <2 default> doSomethingAndReturnSomeValue()); 4 | <1 default> CompletableFuture.runAsync(() -> doSomething()); 5 | <4 default>CompletableFuture<T> future = 6 | <4 default> CompletableFuture.anyOf(completableFutures 7 | <4 default> .toArray(<4 keyword>new <4 default>CompletableFuture[0])); 8 | <3 default>CompletableFuture<Void> future = 9 | <3 default> CompletableFuture.allOf(completableFutures 10 | <3 default> .toArray(<3 keyword>new<3 default> CompletableFuture[0])); 11 | 12 | <6 keyword> try { 13 | <6 default> Object value = future.join();<6 comment>// Blocks until result ready or exception. 14 | <6 keyword> } catch <5 default>(CompletionException e) {..}<6 comment>//RuntimeException 15 | 16 | <5 keyword> try { 17 | <5 default> Object value = future.get();// Blocks until result ready or exception. 18 | <5 keyword> } catch <5 default>(InterruptedException 19 | <5 default> | ExecutionException e) {..} 20 | <5 default> | CompletionException e) {..} 21 | <8 default>completableFuture.thenRun(<9 default>()-> someRunMethod()<8 default>); 22 | <7 keyword> try { 23 | <7 default> Object value = future.getNow("valueIfAbsent"); 24 | <7 keyword> } catch <5 default>(CompletionException e) {..}<6 comment>//RuntimeException 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/snippets/completion-service.html: -------------------------------------------------------------------------------- 1 | <0 comment>// Launch many Callables to the 2 | // completion service. 3 | // Results are queued as they arrive. 4 | // To retrieve the results in sequence, 5 | // call completionService.take().get(); 6 | 7 | // Create a completionService, providing 8 | // an Executor in the constructor.
<0 keyword>final<0 default> CompletionService completionService 9 | = <0 keyword>new<0 default> ExecutorCompletionService( 10 | Executors.newFixedThreadPool(4)); 11 | 12 | <1 comment>// Submit callables to the completion service
<1 default>completionService.submit(callable); 13 | 14 | <2 comment>// Now take results as they complete
<2 default>Future future = completionService.take(); 15 | Result result = future.get(); 16 | -------------------------------------------------------------------------------- /src/main/resources/snippets/countdown-latch.html: -------------------------------------------------------------------------------- 1 | <0 comment>// Constructor - pass in the pass count
<0 keyword>final<0 default> CountDownLatch countDownLatch
= <0 keyword>new<0 default> CountDownLatch(4); 2 | 3 | <1 comment>// Threads attempting to acquire 4 | // will block until the specified 5 | // number of releases is counted.
<1 default>Thread acquireThread = <1 keyword>new <1 default>Thread() { 6 | <1 keyword>public void<1 default> run() { 7 | <1 keyword>try <1 default>{ 8 | countDownLatch.await(); 9 | } <1 keyword>catch<1 default>(InterruptedException e) { } 10 | 11 | <3 comment>// timed await is like await except that 12 | // it times out after the specified 13 | // timeout period.
<3 keyword>try<3 default> { 14 | countDownLatch.await(10L, TimeUnit.SECONDS)); 15 | } <3 keyword>catch<3 default>(InterruptedException e) { } 16 | 17 | <2 default>Thread releaseThread = <2 keyword>new<2 default> Thread(){ 18 | <2 keyword>public void<2 default> run() { 19 | countDownLatch.countDown(); 20 | }} 21 | -------------------------------------------------------------------------------- /src/main/resources/snippets/cyclic-barrier.html: -------------------------------------------------------------------------------- 1 | <0 comment>// Contructor specifies # of parties, and an 2 | // optional Runnable that gets called when the 3 | // barrier is opened.
<0 keyword>final<0 default> CyclicBarrier cyclicBarrier
=<0 keyword> new<0 default> CyclicBarrier(4, new Runnable(){ 4 | <0,4 keyword>public void<0,4 default> run(){ 5 | println("Barrier Action Hit!!"); 6 | }}); 7 | 8 | <1 comment>// Each call to await blocks, until the number 9 | // specified in the constructor is reached. 10 | // Then the Runnable executes and all can pass.
<1 default>Thread thread = <1 keyword>new <1 default>Thread(){ 11 | <1 keyword>public void<1 default> run() { 12 | <1 keyword>try <1 default>{ 13 | cyclicBarrier.await(); 14 | } <1 keyword>catch<1 default>(BrokenBarrierException e) {}}} 15 | 16 | <2 default>Thread thread = <2 keyword>new<2 default> Thread(){ 17 | <2 keyword>public void<2 default> run() { 18 | <2 keyword>try <2 default>{ 19 | cyclicBarrier.await(timeout,TimeUnit.SECONDS); 20 | } <2 keyword>catch<2 default>(BrokenBarrierException e) {}}} 21 | 22 | <3 comment>// reset() allows the barrier to be reused. 23 | // Any waiting threads will throw 24 | // a BrokenBarrierException
<3 default>cyclicBarrier.reset(); 25 | -------------------------------------------------------------------------------- /src/main/resources/snippets/executors.html: -------------------------------------------------------------------------------- 1 | <0 comment>// FixedThreadPool Construction 2 | <0 keyword>final <0 default>Executor executor = 3 | Executors.newFixedThreadPool(<0 literal>4<0 default>); 4 | <2 comment>// Use the Executor to execute a Runnable 5 | <2 default>executor.execute(<2 keyword>new <2 default>Runnable() { 6 | <2 keyword>@Override 7 | public void <2 default>run() { 8 | <2 comment>// Do work 9 | <2 default>} 10 | <5 default>// exit 11 | <2 default>}); 12 | <3 comment>// Use the Executor to submit a Runnable or a Callable 13 | <3 default>Future future = executor.submit(<3 keyword>new <3 default>Runnable() { 14 | <3 default>@Override 15 | <3 keyword>public void <3 default>run() { 16 | <3 comment>// Do work 17 | <3 default>} 18 | <6 default>// exit 19 | <3 default>}); 20 | <4 comment>// Prestarting Core Threads 21 | <4 keyword>int <4 default>count = ((ThreadPoolExecutor)executor) 22 | .prestartAllCoreThreads(); 23 | -------------------------------------------------------------------------------- /src/main/resources/snippets/phaser.html: -------------------------------------------------------------------------------- 1 | <0 default> 2 | Phaser phaser = <0 keyword>new <0 default>Phaser(<0 literal>4<0 default>) { 3 | <0 default>@Override 4 | <0,7 comment>// Perform when all parties arrive<0,7 default>
<0,7 keyword>protected boolean <0,7 default>onAdvance(<0,7 keyword>int <0,7 default>phase, 5 | <0,7 keyword>int <0,7 default>registeredParties) { 6 | <0,7 comment>// return true if the Phaser should 7 | <0,7 comment>// terminate on advance, else false<0,7 default>
<0,7 keyword>return false<0,7 default>; 8 | } 9 | <0 default>}; 10 | <4 keyword>int <4 default>phase = phaser.awaitAdvance(<4 keyword>int <4 default>phase); 11 | <1 keyword>int <1 default>phase = phaser.arrive(); 12 | <3 keyword>int <3 default>phase = phaser.arriveAndAwaitAdvance(); 13 | <2 keyword>int <2 default>phase = phaser.arriveAndDeregister(); 14 | <5 keyword>int <5 default>phase = phaser.register(); 15 | <6 keyword>int <6 default>phase 16 | = phaser.bulkRegister(<6 keyword>int <6 default><2=additional parties>); 17 | 18 | <8 keyword>int <8 default>phase 19 | = phaser.getPhase(); 20 | -------------------------------------------------------------------------------- /src/main/resources/snippets/read-write-lock.html: -------------------------------------------------------------------------------- 1 | <0 keyword>final<0 default> ReadWriteLock readWriteLock = 2 | <0 keyword>new<0 default> ReentrantReadWriteLock(); 3 | <0 comment>new ReentrantReadWriteLock(true); 4 | 5 | <1 comment>// Acquire the read lock 6 | <1 keyword>try <1 default> { 7 | readWriteLock.readLock().lock(); 8 | <1 comment>// or 9 | <1 default> readWriteLock.readLock().tryLock(1L, TimeUnit.SECONDS); 10 | }<1 keyword>catch<1 default>(InterruptedException e){} 11 | 12 | <2 keyword>try <2 default> { 13 | readWriteLock.writeLock().lock(); 14 | <2 comment>// or 15 | <2 default> readWriteLock.writeLock().tryLock(1L, TimeUnit.SECONDS); 16 | }<2 keyword>catch<2 default>(InterruptedException e){} 17 | 18 | <3 default>readWriteLock.readLock().unlock() 19 | <3 comment>// or 20 | <3 default>readWriteLock.writeLock().unlock() 21 | 22 | <4 comment>// Downgrade write lock to a read lock 23 | <4 keyword>try <4 default> { 24 | readWriteLock.readLock().lock(); 25 | readWriteLock.writeLock().unlock(); 26 | }<4 keyword>catch<4 default>(InterruptedException e){} 27 | 28 | <5 default>thread.interrupt(); 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/snippets/reentrant-lock.html: -------------------------------------------------------------------------------- 1 | <0 keyword>final <0 default>Lock lock = <0 keyword>new <0 default>ReentrantLock(); 2 | <1 default>lock.lock(); 3 | <4 default>// do work 4 | <4 keyword>try<4 default> { 5 | lock.lockInterruptibly(); 6 | } <4 keyword>catch <4 default>(InterruptedException e) {...} 7 | <2 default>lock.unlock(); 8 | <3 keyword>boolean <3 default>acquired = <3 literal>false<3 default>; 9 | <3 keyword>try<3 default> { 10 | acquired = lock.tryLock(<3 literal>1L<3 default>, TimeUnit.SECONDS); 11 | <3 keyword>if<3 default>(acquired) { 12 | doSomething(); 13 | } <3 keyword>catch<3 default> (InterruptedException e) {... 14 | } <3 keyword>finally { 15 | if <3 default>(acquired) { 16 | lock.unlock(); 17 | } 18 | } 19 | <5 default>Condition condition = lock.newCondition(); 20 | <6 keyword>try<6 default> { 21 | condition.await(); 22 | } <6 keyword>catch <6 default>(InterruptedException e) {...} 23 | lock.unlock(); 24 | <7 default>lock.lock(); 25 | <7 default>// do work 26 | <8 default>condition.signal(); 27 | lock.unlock(); 28 | <9 default>condition.signalAll(); 29 | lock.unlock(); 30 | <10 default><waitingThread>.interrupt(); 31 | <11 default><interrubtibleThread>.interrupt(); 32 | -------------------------------------------------------------------------------- /src/main/resources/snippets/semaphore.html: -------------------------------------------------------------------------------- 1 | <0 keyword>final <0 default>Semaphore semaphore 2 | = <0 keyword>new<0 default> Semaphore(<0 literal>4<6 default>); 3 | = <6 keyword>new<6 default> Semaphore(<6 literal>4,<6 keyword> true<6 default>); 4 | <1 keyword>try <1 default>{ 5 | semaphore.acquire(); 6 | }<1 keyword> catch <1 default>(InterruptedException e) { } 7 | 8 | <8 default>semaphore.acquireUninterruptibly(); 9 | 10 | <4 keyword>try<4 default> { 11 | <4 keyword> if<4 default>(semaphore.tryAcquire(<4 literal>3<4 default>, TimeUnit.SECONDS)) { 12 | <4 comment> // Do something 13 | <4 default> } 14 | } <4 keyword><4 keyword>catch<4 default> (InterruptedException e) { } 15 | 16 | <3 comment>// If no time is specified, times out immediately 17 | <3 comment>// if not acquired 18 | <3 keyword>if<3 default>(semaphore.tryAcquire()) { 19 | <3 comment> // Do something 20 | <3 default>} 21 | <2 default>semaphore.release(); 22 | <5 default>int count = semaphore.drainPermits(); 23 | <7 default>thread.interrupt(); 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/snippets/stamped-lock.html: -------------------------------------------------------------------------------- 1 | <0 default>StampedLock lock = <0 keyword>new <0 default>StampedLock();
2 | <1 keyword>long <1 default>stamp = lock.readLock();<1 comment>//methods return a stamp if successful else 0.
3 | <6 keyword>long <6 default>stamp = lock.writeLock();
4 | <5 keyword>long <5 default>stamp = lock.tryOptimisticRead();
5 | <2 default>lock.unlockRead(stamp);
6 | <3 default>lock.unlockWrite(stamp);
7 | <4 keyword>boolean <4 default>valid = lock.validate(stamp);
8 | <7 keyword>long <7 default>stamp = lock.tryConvertToWriteLock(stamp);
9 | <8 keyword>long <8 default>stamp = lock.tryConvertToReadLock(stamp);
10 | <9 keyword>long <9 default>stamp = lock.tryConvertToOptimisticRead(stamp);
11 | -------------------------------------------------------------------------------- /src/main/resources/snippets/synchronized.html: -------------------------------------------------------------------------------- 1 | <0 default>Thread thread = Thread.ofPlatform() 2 | .name(String.format("JCA Thread")) 3 | .start(runnable); 4 | 5 | <1 keyword>synchronized <1 default>(object) { 6 | // do some work 7 | <3 keyword>try { 8 | <3 keyword>while<3 default>(!condition){ 9 | <3 default> object.wait(); 10 | <3 default>}} <3 keyword>catch <3 default>(InterruptedException e) { 11 | Thread.currentThread().interrupt(); 12 | <2 default>// releasing lock 13 | <1 default>} 14 | <4 keyword>synchronized <4 default>(object) { 15 | condition = true; 16 | object.notify(); } 17 | <7 keyword>sleep<7 default>(5000) { 18 | <5 keyword>ized <5 default>(object) { 19 | condition = true; 20 | object.notifyAll(); } 21 | <6 default>thread.interrupt(); 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
Thread State Color Codes
gReen = RunnableBLue = BLockedWhite = Waiting 
gray = timed_waitingRed = teRminated 
White Flag = interrupted 
 
34 | -------------------------------------------------------------------------------- /src/main/resources/snippets/transfer-queue.html: -------------------------------------------------------------------------------- 1 | <0 keyword>TransferQueue is really a blocking queue, 2 | but it adds the transfer() method, which blocks if there are no takers 3 | 4 | <0 default>TransferQueue<T> transferQueue 5 | = <0 keyword>new <0 default>LinkedTransferQueue<T>(); 6 | 7 | <1 keyword>try <1 default>{ 8 | transferQueue.transfer(t); 9 | } <1 keyword>catch<1 default> (InterruptedException e) { } 10 | 11 | <3 keyword>try <3 default>{ 12 | <3 keyword>boolean<3 default> success = transferQueue.tryTransfer(t); 13 | } <3 keyword>catch<3 default> (InterruptedException e) { } 14 | 15 | <5 keyword>try <5 default>{ 16 | <5 keyword>boolean<5 default> success = transferQueue.tryTransfer(t, 17 | 2, TimeUnit.SECONDS); 18 | } <5 keyword>catch<5 default> (InterruptedException e) { } 19 | 20 | <6 keyword>try <6 default>{ 21 | <6 keyword>boolean<6 default> success = transferQueue.put(t); 22 | } <6 keyword>catch<6 default> (InterruptedException e) { } 23 | 24 | <2 keyword>try <2 default>{ 25 | T t = transferQueue.take(); 26 | } <2 keyword>catch<2 default> (InterruptedException e) { } 27 | 28 | <4 default>T t = transferQueue.poll(); 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/snippets/virtualthreads.html: -------------------------------------------------------------------------------- 1 | <0 default>Thread thread = Thread.ofVirtual() 2 | .name(String.format("JCA Virtual Thread")) 3 | .start(runnable); 4 | 5 | <1 default>Thread.sleep(2500); 6 | 7 | <2 keyword>synchronized <2 default>(object) { 8 | // do some work 9 | <2 keyword>try { 10 | <2 default> object.wait(2500); 11 | <2 keyword> catch <2 default>(InterruptedException e) { 12 | Thread.currentThread().interrupt(); 13 | <2 default>// releasing lock 14 | 15 | <2 default> 16 | } 17 | } 18 | 19 | <4 default> 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
Thread State Color Codes
gReen = RunnableBLue = BLockedWhite = Waiting 
gray = timed_waitingRed = teRminated 
White Flag = interrupted 
 
32 | 33 | -------------------------------------------------------------------------------- /src/test/images/cover-slide-2.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/test/images/cover-slide-2.pptx -------------------------------------------------------------------------------- /src/test/images/neon-light-mesh-freezelight-abstraction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vgrazi/JavaConcurrentAnimatedReboot/f6884214baf0886e6b23e66245afe9b8aed97cee/src/test/images/neon-light-mesh-freezelight-abstraction.jpg -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/BlockingQueueStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.concurrent.ArrayBlockingQueue; 7 | import java.util.concurrent.BlockingQueue; 8 | 9 | public class BlockingQueueStudy { 10 | @Test 11 | public void whatHappensToThreadWhenOfferFails() { 12 | BlockingQueue queue = new ArrayBlockingQueue(1); 13 | new Thread(()->{ 14 | boolean xxx = queue.offer("xxx"); 15 | System.out.println("Done. " + xxx + " 1. queue size:" + queue.size()); 16 | }).start(); 17 | new Thread(()->{ 18 | boolean xxx = queue.offer("xxx"); 19 | System.out.println("Done. " + xxx + " 2. queue size:" + queue.size()); 20 | }).start(); 21 | } 22 | 23 | @AfterEach 24 | public void keepAlive() throws InterruptedException { 25 | Thread.sleep(5_000); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/CompletableFutureStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Random; 7 | import java.util.concurrent.CompletableFuture; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.logging.Logger; 10 | 11 | public class CompletableFutureStudy { 12 | 13 | private Logger logger = Logger.getLogger("CompletableFutureStudy"); 14 | 15 | private void println(String message) { 16 | logger.info(message); 17 | } 18 | 19 | private final Random random = new Random(); 20 | 21 | @Test 22 | public void allOf() { 23 | int testCount = 1; 24 | CompletableFuture[] cfs = new CompletableFuture[testCount]; 25 | for (int i = 0; i < testCount; i++) { 26 | println("Creating CompletableFuture " + "CompletableFuture #" + (i+1)); 27 | CompletableFuture completableFuture = addCompletableFuture("CompletableFuture #" + (i+1), 5); 28 | cfs[i] = completableFuture; 29 | } 30 | CompletableFuture allOf = CompletableFuture.allOf(cfs); 31 | allOf.join(); 32 | try { 33 | allOf.get(); 34 | } catch (InterruptedException | ExecutionException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | 39 | @Test 40 | public void anyOf() { 41 | int testCount = 5; 42 | CompletableFuture[] cfs = new CompletableFuture[testCount]; 43 | for (int i = 0; i < testCount; i++) { 44 | println("Creating CompletableFuture " + "CompletableFuture #" + (i+1)); 45 | CompletableFuture completableFuture = addCompletableFuture("CompletableFuture #" + (i+1), 5); 46 | cfs[i] = completableFuture; 47 | } 48 | CompletableFuture anyOf = CompletableFuture.anyOf(cfs); 49 | CompletableFuture.allOf(cfs).join(); 50 | Object joinAny = anyOf.join(); 51 | println("Joined!!!:" + joinAny); 52 | try { 53 | Object joinGet = anyOf.get(); 54 | println("Joined!!!:" + joinGet); 55 | } catch (InterruptedException | ExecutionException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | 60 | @Test 61 | public void thenDo(){ 62 | int testCount = 2; 63 | CompletableFuture[] cfs = new CompletableFuture[testCount]; 64 | for (int i = 0; i < testCount; i++) { 65 | println("Creating CompletableFuture " + "CompletableFuture #" + (i+1)); 66 | CompletableFuture completableFuture = addCompletableFuture("CompletableFuture #" + (i+1), 5); 67 | cfs[i] = completableFuture; 68 | } 69 | CompletableFuture anyOf = CompletableFuture.anyOf(cfs); 70 | anyOf.thenRun(()-> println("I'm Running!!!!")); 71 | anyOf.thenApply(x -> { 72 | println("I'm Returning " + x + " " + x.getClass() + " !!!!"); 73 | return x; 74 | }); 75 | anyOf.thenAccept(x -> { 76 | println("I'm Accepting " + x + " " + x.getClass() + " !!!!"); 77 | }); 78 | Object joinAny = anyOf.join(); 79 | println("Joined!!!:" + joinAny); 80 | try { 81 | Object joinGet = anyOf.get(); 82 | println("Joined!!!:" + joinGet); 83 | } catch (InterruptedException | ExecutionException e) { 84 | e.printStackTrace(); 85 | } 86 | 87 | } 88 | 89 | private CompletableFuture addCompletableFuture(String id, int timeSecs) { 90 | CompletableFuture f1 = CompletableFuture.supplyAsync(()->{ 91 | try { 92 | long sleepTime = (random.nextInt(timeSecs) + 1)*1000; 93 | Thread.sleep(sleepTime); 94 | } catch (InterruptedException e) { 95 | e.printStackTrace(); 96 | } 97 | 98 | println("Completing CompletableFuture " + id); 99 | return id; 100 | }); 101 | return f1; 102 | } 103 | 104 | @AfterEach 105 | public void keepAlive() throws InterruptedException { 106 | Thread threadKeepAlive = new Thread(() -> { 107 | // THIS IS A KEEPALIVE THREAD 108 | Object mutex = new Object(); 109 | synchronized (mutex) { 110 | try { 111 | mutex.wait(5_000); 112 | } catch (InterruptedException e) { 113 | e.printStackTrace(); 114 | } 115 | } 116 | }); 117 | threadKeepAlive.start(); 118 | threadKeepAlive.join(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/CompletionServiceStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import java.util.concurrent.*; 4 | import java.util.logging.Logger; 5 | 6 | public class CompletionServiceStudy { 7 | 8 | private static Logger logger = Logger.getLogger("CompletionServiceStudy"); 9 | private static void println(Object message) { 10 | logger.info(String.valueOf(message)); 11 | } 12 | private static Thread.State lastState; 13 | 14 | public static void main(String[] args) throws InterruptedException, ExecutionException { 15 | CompletionService completionService = new ExecutorCompletionService(Executors.newCachedThreadPool()); 16 | Future take = completionService.take(); 17 | println(take); 18 | printThreadState(); 19 | completionService.submit(() -> { 20 | try { 21 | for(int i = 0; i < 5; i++) { 22 | printThreadState(); 23 | println("Still there"); 24 | Thread.sleep(1000); 25 | } 26 | } catch (InterruptedException e) { 27 | Thread.currentThread().interrupt(); 28 | } 29 | printThreadState(); 30 | println("Exiting"); 31 | return "Complete"; 32 | }); 33 | println("Waiting for result"); 34 | String rval = completionService.take().get(); 35 | println("returned " + rval); 36 | } 37 | 38 | private static void printThreadState() { 39 | Thread.State state = Thread.currentThread().getState(); 40 | if(state != lastState) { 41 | println(state); 42 | lastState = state; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/CountdownLatchStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.atomic.AtomicBoolean; 5 | 6 | public class CountdownLatchStudy { 7 | public static void main(String[] args) { 8 | AtomicBoolean success =new AtomicBoolean(true); 9 | CountDownLatch latch=new CountDownLatch(1); 10 | Thread thread1 = new Thread(()->{ 11 | try { 12 | Thread.currentThread().interrupt(); 13 | latch.await(); 14 | success.set(false); 15 | 16 | } catch(InterruptedException e) { 17 | e.printStackTrace(); 18 | } 19 | }); 20 | thread1.start(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/ExchangerStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.swing.*; 7 | import java.util.Random; 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.Exchanger; 10 | import java.util.stream.IntStream; 11 | 12 | 13 | public class ExchangerStudy { 14 | static Logger logger = LoggerFactory.getLogger(ExchangerStudy.class); 15 | final static Random random = new Random(); 16 | public static void main(String[] args) { 17 | new ExchangerStudy().launch(); 18 | 19 | JFrame jFrame = new JFrame(); 20 | jFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 21 | jFrame.setVisible(true); 22 | } 23 | 24 | private void launch() { 25 | Exchanger exchanger = new Exchanger<>(); 26 | runIt(exchanger, 0); 27 | runIt(exchanger, 1); 28 | } 29 | 30 | private void runIt(Exchanger exchanger, int seed) { 31 | CompletableFuture.runAsync(()-> IntStream.iterate(seed, i -> i + 2).forEach(i -> 32 | { 33 | try { 34 | if (seed == 0) { 35 | int timeout = 1000 + random.nextInt(1000); 36 | synchronized (this) { 37 | logger.info(String.format("Thread (%s) waiting %d", seed,timeout)); 38 | wait(timeout); 39 | logger.info(String.format("Thread (%s) woke", seed)); 40 | } 41 | } 42 | logger.info(String.format("(%s) exchanging %d", seed, i)); 43 | int from = exchanger.exchange(i); 44 | logger.info(String.format("(%s) Exchanged %s for %s", seed, i, from)); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } 48 | })); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/ExecutorsStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.concurrent.*; 8 | import java.util.logging.Logger; 9 | 10 | public class ExecutorsStudy { 11 | private static Logger logger = Logger.getLogger("ExecutorsStudy"); 12 | private static void println(Object message) { 13 | logger.info(String.valueOf(message)); 14 | } 15 | private boolean stopped; 16 | 17 | @Test 18 | public void testPrestart() { 19 | ThreadPoolExecutor service = (ThreadPoolExecutor) Executors.newFixedThreadPool(4); 20 | int threadCount = service.prestartAllCoreThreads(); 21 | service.submit(() -> { 22 | // while (working) { 23 | // doSomeWork(); 24 | // } 25 | // // done 26 | }); 27 | } 28 | 29 | @Test 30 | public void saturationPolicy() throws InterruptedException { 31 | Executor executor = new ThreadPoolExecutor(0,2,2, TimeUnit.SECONDS, new SynchronousQueue<>()); 32 | // Executor executor = Executors.newFixedThreadPool(2); 33 | // ((ThreadPoolExecutor) executor).setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); 34 | // ((ThreadPoolExecutor) executor).setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); 35 | ((ThreadPoolExecutor) executor).setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 36 | // ((ThreadPoolExecutor) executor).setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 37 | BlockingQueue queue = new LinkedBlockingDeque(); 38 | Collection list = new ArrayList(); 39 | queue.drainTo(list); 40 | execute(executor); 41 | execute(executor); 42 | execute(executor); 43 | keepAlive(); 44 | } 45 | 46 | private void execute(Executor executor) { 47 | executor.execute(()->{ 48 | println("Launching " + Thread.currentThread()); 49 | while(!stopped) { 50 | Thread.yield(); 51 | } 52 | println("Exiting " + Thread.currentThread()); 53 | }); 54 | } 55 | 56 | @Test 57 | public void handler() throws InterruptedException { 58 | ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0, TimeUnit.MILLISECONDS, 59 | new SynchronousQueue<>(), 60 | new ThreadPoolExecutor.CallerRunsPolicy()); 61 | executor.setRejectedExecutionHandler(executor.getRejectedExecutionHandler()); 62 | for (int i = 0; i < 6; i++) { 63 | addRunnable(executor); 64 | } 65 | keepAlive(); 66 | } 67 | 68 | @Test 69 | public void tryAgain() { 70 | ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, 71 | new SynchronousQueue<>(), 72 | new ThreadPoolExecutor.AbortPolicy()); 73 | 74 | executor.execute(() -> waitFor(250)); 75 | 76 | long startTime = System.currentTimeMillis(); 77 | executor.execute(() -> waitFor(500)); 78 | long blockedDuration = System.currentTimeMillis() - startTime; 79 | 80 | // assertThat(blockedDuration).isGreaterThanOrEqualTo(500); 81 | } 82 | 83 | private void waitFor(int ms) { 84 | try { 85 | Thread.sleep(ms); 86 | } catch (InterruptedException e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | private void keepAlive() throws InterruptedException { 92 | Thread threadKeepAlive = new Thread(() -> { 93 | // THIS IS A KEEPALIVE THREAD 94 | while (true) { 95 | Thread.yield(); 96 | } 97 | }); 98 | threadKeepAlive.start(); 99 | threadKeepAlive.join(); 100 | } 101 | 102 | 103 | private void addRunnable(ExecutorService executorService) { 104 | println("Trying " + Thread.currentThread()); 105 | executorService.execute(()->{ 106 | println("Launching " + Thread.currentThread()); 107 | while(!stopped) { 108 | Thread.yield(); 109 | } 110 | println("Completing " + Thread.currentThread()); 111 | }); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/Main.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Container; 5 | import java.util.logging.Logger; 6 | 7 | import javax.swing.JFrame; 8 | import javax.swing.JTextPane; 9 | import javax.swing.text.Element; 10 | import javax.swing.text.html.HTMLDocument; 11 | import javax.swing.text.html.HTMLEditorKit; 12 | import javax.swing.text.html.StyleSheet; 13 | 14 | public class Main extends JFrame { 15 | private static Logger logger = Logger.getLogger("Main"); 16 | private static void println(Object message) { 17 | logger.info(String.valueOf(message)); 18 | } 19 | 20 | StyleSheet styleSheet = new StyleSheet(); 21 | HTMLDocument htmlDocument; 22 | HTMLEditorKit htmlEditorKit = new HTMLEditorKit(); 23 | Element bodyElement; 24 | 25 | public static void main(String[] args) throws Exception { 26 | Main jTextPaneApp = new Main(); 27 | jTextPaneApp.setVisible(true); 28 | // for (int i =0; i < 10; i++) { 29 | // Thread.currentThread().sleep(3000); 30 | // jTextPaneApp.change(i); 31 | // } 32 | } 33 | 34 | public Main() { 35 | setSize(400, 400); 36 | htmlEditorKit.setStyleSheet(styleSheet); 37 | htmlDocument = (HTMLDocument) htmlEditorKit.createDefaultDocument(); 38 | JTextPane jTextPane = new JTextPane(); 39 | jTextPane.setEditorKit(htmlEditorKit); 40 | jTextPane.setDocument(htmlDocument); 41 | 42 | try { 43 | Element htmlElement = htmlDocument.getRootElements()[0]; 44 | htmlDocument.setInnerHTML(htmlElement, "" + 45 | " \n" + 46 | " \n" + 47 | " An example HTMLDocument\n" + 48 | " \n" + 50 | " \n" + 51 | " \n" + 52 | "
\n" + 53 | "

Paragraph 1-

\n" + 54 | "

Paragraph 2-

\n" + 55 | "
\n" + 56 | " \n" + 57 | " " + 58 | "" 59 | ); 60 | bodyElement = htmlElement.getElement(0); 61 | 62 | Container contentPane = getContentPane(); 63 | contentPane.add(jTextPane, BorderLayout.CENTER); 64 | super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 65 | 66 | } catch (Exception e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | 71 | public void reapplyStyles() { 72 | Element sectionElem = htmlDocument.getElement("main"); 73 | if (sectionElem != null) { 74 | int paraCount = sectionElem.getElementCount(); 75 | for (int i = 0; i < paraCount; i++) { 76 | Element e = sectionElem.getElement(i); 77 | int rangeStart = e.getStartOffset(); 78 | int rangeEnd = e.getEndOffset(); 79 | htmlDocument.setParagraphAttributes(rangeStart, rangeEnd - rangeStart, 80 | e.getAttributes(), true); 81 | } 82 | } 83 | } 84 | 85 | boolean even; 86 | 87 | public void change(int i) throws Exception { 88 | println("Changing"); 89 | Element div = htmlDocument.getElement("main"); 90 | String htmlText; 91 | switch(i%3) { 92 | case 0: 93 | htmlText = "
hello
"; 94 | break; 95 | case 1: 96 | htmlText = "
goodbye
"; 97 | break; 98 | case 2: 99 | default: 100 | htmlText = "" + 101 | "
styledText" + 102 | "
"; 103 | break; 104 | } 105 | setSnippet(htmlText); 106 | if (even) { 107 | styleSheet.addRule(".synchronized {color: blue;}"); 108 | } else { 109 | styleSheet.addRule(".synchronized {color: red;}"); 110 | } 111 | reapplyStyles(); 112 | even = !even; 113 | } 114 | 115 | private void setSnippet(String content) throws Exception { 116 | Element div = htmlDocument.getElement("main"); 117 | htmlDocument.setInnerHTML(div, content); } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/PhaserStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import java.util.concurrent.Phaser; 4 | 5 | public class PhaserStudy { 6 | public void test() { 7 | Phaser phaser = new Phaser(4) { 8 | @Override 9 | // Perform when all parties arrive 10 | protected boolean onAdvance(int phase, int registeredParties) { 11 | // return true if the phaser should 12 | // terminate on advance, else false; 13 | return false; 14 | } 15 | }; 16 | 17 | int phase = phaser.arriveAndAwaitAdvance(); 18 | 19 | phase = phaser.arrive(); 20 | 21 | int in_phase = 1; 22 | phase = phaser.awaitAdvance(in_phase); 23 | 24 | phase = phaser.arriveAndDeregister(); 25 | 26 | phase = phaser.register(); 27 | 28 | int parties = 3; 29 | phase = phaser.bulkRegister(parties); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/QueueStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | import java.util.logging.Logger; 6 | 7 | public class QueueStudy { 8 | private static Logger logger = Logger.getLogger("QueueStudy"); 9 | private static void println(Object message) { 10 | logger.info(String.valueOf(message)); 11 | } 12 | 13 | public static void main(String[] args) { 14 | Queue queue = new LinkedList<>(); 15 | queue.offer(1L); 16 | queue.offer(2L); 17 | queue.offer(3L); 18 | queue.offer(4L); 19 | queue.offer(5L); 20 | 21 | while (!queue.isEmpty()) { 22 | Long poll = queue.poll(); 23 | println(poll); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/ReactorStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import rx.Observable; 4 | import rx.Scheduler; 5 | import rx.observables.ConnectableObservable; 6 | import rx.schedulers.Schedulers; 7 | 8 | public class ReactorStudy { 9 | public static void main(String[] args) { 10 | 11 | System.out.println("Main thread:" + Thread.currentThread()); 12 | Scheduler scheduler = Schedulers.trampoline(); 13 | // Scheduler scheduler = Schedulers.from(Executors.newFixedThreadPool(2)); 14 | // Scheduler scheduler = Schedulers.immediate(); 15 | try { 16 | 17 | for(int i = 0; i < 10; i++) { 18 | Thread.sleep(100); 19 | } 20 | } catch (InterruptedException e) { 21 | e.printStackTrace(); 22 | } 23 | 24 | // Scheduler scheduler = Schedulers.from(command -> new Thread(() -> { 25 | // println(Thread.currentThread()); 26 | // command.run(); 27 | // }).start()); 28 | 29 | Observable test = Observable.from(new String[]{"this", "is", "a", "test", "one", "two", "three"}) 30 | .subscribeOn(scheduler); 31 | Observable gettys = Observable.from(new String[]{"four", "score", "and", "seven", "years", "ago"}) 32 | .subscribeOn(scheduler); 33 | // if (true) 34 | { 35 | System.out.println("1111111111111publish"); 36 | ConnectableObservable testPublish = test.publish(); 37 | ConnectableObservable gettysPublish = gettys.publish(); 38 | testPublish.subscribe(x1 -> System.out.println(x1 + " ... " + Thread.currentThread())); 39 | gettysPublish.subscribe(x1 -> System.out.println(x1 + " ... " + Thread.currentThread())); 40 | testPublish.zipWith(gettysPublish, 41 | (x, y) -> x + "-" + y) 42 | .subscribe(x1 -> { 43 | System.out.println(x1 + "..." + Thread.currentThread()); 44 | }); 45 | testPublish.connect(); 46 | gettysPublish.connect(); 47 | 48 | } 49 | // else 50 | { 51 | System.out.println("22222222222222222 non publish"); 52 | test.subscribe(x1 -> System.out.println(x1 + " ... " + Thread.currentThread())); 53 | gettys.subscribe(x1 -> System.out.println(x1 + " ... " + Thread.currentThread())); 54 | test.zipWith(gettys, 55 | (x, y) -> x + "-" + y) 56 | .subscribe(x1 -> { 57 | System.out.println(x1 + "..." + Thread.currentThread()); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/ReentrantLockStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.concurrent.CountDownLatch; 6 | import java.util.concurrent.locks.Condition; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | 9 | import static org.junit.jupiter.api.Assertions.fail; 10 | 11 | public class ReentrantLockStudy{ 12 | @Test 13 | public void testAwaitAfterInterrupted() throws InterruptedException{ 14 | 15 | // spin thread1 16 | // thread1 grab lock 17 | // call thread1.interrupt 18 | // thread1 call await 19 | // expect an InterruptedException 20 | CountDownLatch latch = new CountDownLatch(1); 21 | final boolean[] expected = {true}; 22 | boolean[] flag = new boolean[]{true}; 23 | // create the lock 24 | ReentrantLock lock = new ReentrantLock(); 25 | Thread thread1 = new Thread(()-> 26 | { 27 | lock.lock(); 28 | // loop until flag becomes false, after interrupt is called 29 | while(flag[0]); 30 | // interrupt was called. Now await and prove the interrupt falls thru 31 | Condition condition = lock.newCondition(); 32 | try{ 33 | Thread.currentThread().interrupt(); 34 | condition.await(); 35 | System.out.println("Thread 1 Exiting"); 36 | // Interrupt never happened. test failed 37 | expected[0] = false; 38 | }catch(InterruptedException e){ 39 | e.printStackTrace(); 40 | // test passed 41 | } 42 | latch.countDown(); 43 | }); 44 | System.out.println("Starting thread1"); 45 | thread1.start(); 46 | System.out.println("Interrupting thread1"); 47 | thread1.interrupt(); 48 | // set flag to false to force the loop to exit 49 | System.out.println("Setting flag=false to signal thread1 to await()"); 50 | flag[0] = false; 51 | latch.await(); 52 | if(!expected[0]){ 53 | fail(); 54 | } 55 | } 56 | 57 | @Test 58 | public void testInterruptOnPlainLock() throws InterruptedException { 59 | // spin thread1 60 | // let it grab a lock 61 | // and sleep 62 | // spin thread2 63 | // let it grab the same lock (and wait) 64 | // interrupt thread2 65 | // see what it does 66 | boolean flag = true; 67 | // create the lock 68 | ReentrantLock lock = new ReentrantLock(); 69 | Thread thread1 = new Thread(()-> 70 | { 71 | lock.lock(); 72 | while(flag); 73 | System.out.println("Thread 1 Exiting"); 74 | }); 75 | Thread thread2 = new Thread(()-> 76 | { 77 | try { 78 | lock.lockInterruptibly(); 79 | System.out.println("Thread 2 got lock"); 80 | } catch(InterruptedException e) { 81 | e.printStackTrace(); 82 | } 83 | }); 84 | thread1.start(); 85 | Thread.sleep(500); 86 | thread2.start(); 87 | System.out.println("Interrupting"); 88 | thread2.interrupt(); 89 | System.out.println("Interrupted"); 90 | 91 | // see what happens if a non-interruptible thread is interrupted. Test this when it is waiting for the lock, and when it has the lock 92 | } 93 | public static void main(String[] args){ 94 | ReentrantLock lock = new ReentrantLock(); 95 | lock.lock(); 96 | 97 | /// do work 98 | Condition condition = lock.newCondition(); 99 | try{ 100 | condition.await(); 101 | }catch(InterruptedException e){ 102 | Thread.currentThread().interrupt(); 103 | } 104 | 105 | lock.unlock(); 106 | 107 | lock.lock(); 108 | 109 | /// do work 110 | condition.signal(); 111 | condition.signalAll(); 112 | lock.unlock(); 113 | 114 | 115 | try{ 116 | lock.lockInterruptibly(); 117 | // do work 118 | lock.unlock(); 119 | }catch(InterruptedException e){ 120 | // interrupted 121 | }finally{ 122 | if(lock.isHeldByCurrentThread()){ 123 | lock.unlock(); 124 | } 125 | } 126 | 127 | Thread.currentThread().interrupt(); 128 | 129 | boolean success = lock.tryLock(); 130 | if(success){ 131 | // do work 132 | lock.unlock(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/StampedLockStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.concurrent.locks.StampedLock; 7 | import java.util.logging.Logger; 8 | 9 | public class StampedLockStudy { 10 | Logger logger = Logger.getLogger("StampedLockStudy"); 11 | private boolean stopped; 12 | 13 | enum Types { 14 | read, write 15 | } 16 | @Test 17 | public void testReadWriteLocking() throws InterruptedException { 18 | StampedLock lock = new StampedLock(); 19 | spin(lock, 1, Types.read); 20 | spin(lock, 2, Types.read); 21 | Thread.sleep(500); 22 | spin(lock, 3, Types.write); 23 | } 24 | 25 | private void spin(StampedLock lock, int id, Types type) { 26 | new Thread(()->{ 27 | logger.info(String.format("Thread %d acquiring %s lock.%n", id, type)); 28 | long stamp = type==Types.read? lock.readLock():lock.writeLock(); 29 | logger.info(String.format("Thread %d acquired %s lock. Stamp: %d%n", id, type, stamp)); 30 | try { 31 | Thread.sleep(1000); 32 | } catch (InterruptedException e) { 33 | e.printStackTrace(); 34 | } 35 | // long releaseStamp = -stamp; 36 | long releaseStamp = stamp; 37 | unlock(lock, id, type, releaseStamp); 38 | // lock.unlock(releaseStamp); 39 | // logger.info(String.format("Thread %d released %s lock. Stamp: %d%n", id, type, releaseStamp)); 40 | }).start(); 41 | } 42 | 43 | private void unlock(StampedLock lock, int id, Types type, long stamp) { 44 | if (type == Types.read) { 45 | lock.unlockRead(stamp); 46 | } else { 47 | lock.unlockWrite(stamp); 48 | } 49 | logger.info(String.format("Thread %d released %s lock. Stamp: %d%n", id, type, stamp)); 50 | } 51 | 52 | @AfterEach 53 | public void keepAlive() throws InterruptedException { 54 | Thread threadKeepAlive = new Thread(() -> { 55 | // THIS IS A KEEPALIVE THREAD 56 | Object mutex = new Object(); 57 | synchronized (mutex) { 58 | try { 59 | mutex.wait(3_000); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | }); 65 | threadKeepAlive.start(); 66 | threadKeepAlive.join(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/StyleStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import javax.swing.*; 4 | import javax.swing.text.BadLocationException; 5 | import javax.swing.text.Element; 6 | import javax.swing.text.html.HTMLDocument; 7 | import javax.swing.text.html.HTMLEditorKit; 8 | import javax.swing.text.html.StyleSheet; 9 | import java.awt.*; 10 | import java.io.IOException; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.ScheduledExecutorService; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.logging.Logger; 15 | 16 | public class StyleStudy extends JFrame { 17 | 18 | private StyleSheet styleSheet = new StyleSheet(); 19 | private HTMLDocument htmlDocument; 20 | private final String blanks = " "; // using Java 8 :( 21 | private static Logger logger = Logger.getLogger("StyleStudy"); 22 | private static void println(Object message) { 23 | logger.info(String.valueOf(message)); 24 | } 25 | 26 | public static void main(String[] args) { 27 | StyleStudy stylePlay = new StyleStudy(); 28 | stylePlay.setVisible(true); 29 | } 30 | 31 | public StyleStudy() { 32 | setSize(400, 400); 33 | HTMLEditorKit htmlEditorKit = new HTMLEditorKit(); 34 | htmlDocument = (HTMLDocument) htmlEditorKit.createDefaultDocument(); 35 | JTextPane jTextPane = new JTextPane(); 36 | jTextPane.setEditorKit(htmlEditorKit); 37 | jTextPane.setDocument(htmlDocument); 38 | 39 | try { 40 | Element htmlElement = htmlDocument.getRootElements()[0]; 41 | htmlDocument.setInnerHTML(htmlElement, "" + 42 | " \n" + 43 | " \n" + 44 | "
\n" + 45 | "

Some text

\n" + 46 | "
\n" + 47 | " \n" + 48 | " \n" + 49 | "" 50 | ); 51 | 52 | // addInnerContent("" + 53 | // " \n" + 54 | // "
\n" + 55 | // " Some text\n" + 56 | // "
\n" + 57 | // " \n" + 58 | // "" 59 | // ); 60 | 61 | displayHtml(null, 0); 62 | htmlEditorKit.setStyleSheet(styleSheet); 63 | add(jTextPane, BorderLayout.CENTER); 64 | super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 65 | 66 | ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); 67 | scheduler.schedule(() -> { 68 | try { 69 | addInnerContent("

replaced test 1

"); 70 | // addOuterContent("" + 71 | // "
" + 72 | // "

replaced test 1

" + 73 | // "
"); 74 | } catch (IOException | BadLocationException e) { 75 | e.printStackTrace(); 76 | } 77 | }, 2000, TimeUnit.MILLISECONDS); 78 | scheduler.schedule(() -> { 79 | try { 80 | addInnerContent("

replaced test 2

"); 81 | // addOuterContent("" + 82 | // "
" + 83 | // "

replaced test 1

" + 84 | // "
"); 85 | } catch (IOException | BadLocationException e) { 86 | e.printStackTrace(); 87 | } 88 | }, 4000, TimeUnit.MILLISECONDS); 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | 94 | boolean flip; 95 | private void addInnerContent(String content) throws IOException, BadLocationException { 96 | Element divElement = htmlDocument.getElement("Contents"); 97 | if(divElement == null) { 98 | content = "
" + content + "
"; 99 | addOuterContent(content); 100 | } 101 | 102 | htmlDocument.setInnerHTML(divElement, content); 103 | styleSheet = htmlDocument.getStyleSheet(); 104 | if (flip) { 105 | styleSheet.addRule(".style-1 {color: 'red'; align='left'}"); 106 | } 107 | else { 108 | styleSheet.addRule(".style-1 {color: 'green'; align='left'}"); 109 | } 110 | reapplyStyles(); 111 | flip = !flip; 112 | displayHtml(null, 0); 113 | } 114 | 115 | private void addOuterContent(String content) throws IOException, BadLocationException { 116 | Element divElement = htmlDocument.getElement("Contents"); 117 | 118 | htmlDocument.setOuterHTML(divElement, content); 119 | styleSheet.addRule(".style-1 {color: 'red'; align='left'}"); 120 | reapplyStyles(); 121 | displayHtml(null, 0); 122 | } 123 | 124 | private void reapplyStyles() { 125 | Element sectionElem = htmlDocument.getRootElements()[0]; 126 | int paraCount = sectionElem.getElementCount(); 127 | for (int i = 0; i < paraCount; i++) { 128 | Element e = sectionElem.getElement(i); 129 | int rangeStart = e.getStartOffset(); 130 | int rangeEnd = e.getEndOffset(); 131 | htmlDocument.setParagraphAttributes(rangeStart, rangeEnd - rangeStart, e.getAttributes(), true); 132 | } 133 | } 134 | 135 | private void displayHtml(Element node, int level) throws BadLocationException { 136 | String padding = blanks.substring(0, level * 2); 137 | if (node == null) { 138 | println("==============="); 139 | node = htmlDocument.getRootElements()[0]; 140 | 141 | } 142 | println("****" + padding + node.getName()); 143 | if (node.isLeaf()) { 144 | println("***-" + padding + " " + htmlDocument.getText(node.getStartOffset(), node.getEndOffset() - node.getStartOffset())); 145 | } else { 146 | int count = node.getElementCount(); 147 | for (int i = 0; i < count; i++) { 148 | Element element = node.getElement(i); 149 | displayHtml(element, level + 1); 150 | } 151 | } 152 | } 153 | 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/SynchronizedStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.logging.Logger; 6 | 7 | public class SynchronizedStudy { 8 | private final Object MUTEX = new Object(); 9 | private boolean stopped; 10 | 11 | private static Logger logger = Logger.getLogger("SynchronizedStudy"); 12 | private static void println(Object message) { 13 | logger.info(String.valueOf(message)); 14 | } 15 | 16 | /** 17 | * Demo for how thread.stop will release a blocked thread. 18 | * Calling stop() on the holding thread is the only way for a blocked thread to proactively get unblocked. And since the thread is blocked, 19 | * it can't do this directly 20 | * @throws InterruptedException 21 | */ 22 | @Test 23 | public void testBlockedStop() throws InterruptedException { 24 | Thread thread1 = getThread(); 25 | Thread thread2 = getThread(); 26 | Thread thread3 = getThread(); 27 | // Thread.holdsLock() 28 | 29 | println("Trying interrupt on thread 1"); 30 | thread1.interrupt(); 31 | Thread.sleep(1000); 32 | println("Trying stop on thread 2"); 33 | thread2.stop(); 34 | Thread.sleep(1000); 35 | 36 | println("Trying stop on thread 1"); 37 | thread1.stop(); 38 | Thread.sleep(100); 39 | 40 | println("Exhausted all attempts"); 41 | keepAlive(); 42 | } 43 | 44 | @Test 45 | public void testInterrupt() throws InterruptedException { 46 | Thread thread1 = getThread(); 47 | Thread thread2 = getThread(); 48 | thread1.interrupt(); 49 | keepAlive(); 50 | } 51 | 52 | private void keepAlive() throws InterruptedException { 53 | Thread threadKeepAlive = new Thread(() -> { 54 | // THIS IS A KEEPALIVE THREAD 55 | while (true) { 56 | Thread.yield(); 57 | } 58 | }); 59 | threadKeepAlive.start(); 60 | threadKeepAlive.join(); 61 | } 62 | 63 | private Thread getThread() throws InterruptedException { 64 | Thread thread = new Thread(() -> { 65 | println(Thread.currentThread() + " ACQUIRING LOCK"); 66 | synchronized (MUTEX) { 67 | println(Thread.currentThread() + " HAS LOCK"); 68 | while (!stopped && !Thread.currentThread().isInterrupted()) { 69 | Thread.yield(); 70 | } 71 | println(Thread.currentThread() + "Exiting"); 72 | } 73 | }); 74 | thread.start(); 75 | Thread.sleep(100); 76 | return thread; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/vgrazi/javaconcurrentanimated/study/TransferQueueStudy.java: -------------------------------------------------------------------------------- 1 | package com.vgrazi.javaconcurrentanimated.study; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.concurrent.LinkedTransferQueue; 7 | import java.util.concurrent.TransferQueue; 8 | import java.util.logging.Logger; 9 | 10 | public class TransferQueueStudy { 11 | Logger logger = Logger.getLogger("TransferQueueStudy"); 12 | // @BeforeClass 13 | // public void setLogger() { 14 | // System.setProperty("java.util.logging.SimpleFormatter.format","[%1$tF %1$tT] [%4$-7s] %5$s"); 15 | // } 16 | @Test 17 | public void doesTransferQueueTransferWaitForTaker() throws InterruptedException { 18 | TransferQueue transferQueue = new LinkedTransferQueue(); 19 | new Thread(()->{ 20 | try { 21 | Thread.sleep(1000); 22 | logger.info(Thread.currentThread() + " Taking"); 23 | Object take = transferQueue.take(); 24 | logger.info(Thread.currentThread() + " Took " + take); 25 | } catch (InterruptedException e) { 26 | e.printStackTrace(); 27 | } 28 | }).start(); 29 | logger.info(Thread.currentThread() + " Transferring"); 30 | transferQueue.transfer("xxxx"); 31 | logger.info(Thread.currentThread() + " Transfer complete"); 32 | } 33 | 34 | @Test 35 | public void doesTransferQueuePutWaitForTaker() throws InterruptedException { 36 | TransferQueue transferQueue = new LinkedTransferQueue(); 37 | new Thread(()->{ 38 | try { 39 | Thread.sleep(1000); 40 | logger.info(Thread.currentThread() + " Taking"); 41 | Object take = transferQueue.take(); 42 | logger.info(Thread.currentThread() + " Took " + take); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | }).start(); 47 | logger.info(Thread.currentThread() + " Transferring"); 48 | transferQueue.put("xxxx"); 49 | logger.info(Thread.currentThread() + " Transfer complete"); 50 | } 51 | 52 | @AfterEach 53 | public void keepAlive() throws InterruptedException { 54 | Thread.sleep(5_000); 55 | } 56 | } 57 | --------------------------------------------------------------------------------