/** * CSC543DemoImageAtomic modied from CSC543DemoImageImplicitLock 2/13/2023 * CSC543DemoImageImplicitLock modified from CSC543DemoImagePipeLineQueues 2/13/2023 * CSC543DemoImagePipeLineQueues drafted 1/30/2023 to show Queue and other synchronization * and communication between a worker thread and a non-thread-safe graphical display thread. * D. Parson **/ import java.util.* ; // https://docs.oracle.com/javase/8/docs/api/index.html?java/util/List.html import java.util.concurrent.atomic.* ; // Use when low probability of contention or blocking. import java.util.concurrent.CopyOnWriteArrayList ; import java.io.* ; // This outer class is @NotThreadSafe because the Processing GUI data structures are // not all guarded or otherwise protected by unsynchronized concurrent access. // https://docs.oracle.com/javase/8/docs/api/index.html?java/util/concurrent/package-summary.html // THESE DATA ARE THREAD-SAFE. nonblocking is IMMUTABLE. final boolean nonblocking = false ; // for nonblocking queues tyhread terminates when all images in queue // final Queue imageQueue = new ConcurrentLinkedQueue(); // final BlockingQueue imageQueue = new LinkedBlockingQueue(10); // THESE DATA ARE NON-THREAD-SAFE and used only inside the display() thread. boolean isTimeToRead = true ; // not accessed by FileReaderActiveClass thread PImage currentImage = null ; // not accessed by FileReaderActiveClass thread FileReaderActiveClass workerBehavior ; // variable not accessed by FileReaderActiveClass thread Thread workerThread ; // variable not accessed by FileReaderActiveClass thread void setup() { // Processing's startup function, called once size(1920, 1080); frameRate(60); println("setup()'s thread is " + Thread.currentThread()); try { workerBehavior = new FileReaderActiveClass(); workerThread = new Thread(workerBehavior); workerThread.setPriority(Thread.MAX_PRIORITY); // try to avoid delays workerThread.start(); } catch (FileNotFoundException fx) { println("Photo list text file not found: " + fx.getMessage()); exit(); } } boolean printme = true ; void draw() { if (printme) { println("draw()'s thread is " + Thread.currentThread()); printme = false ; } // Invoked at frameRate background(0); // black imageMode(CENTER); // reference point for image plotting location if (isTimeToRead) { PImageContainer tmpimg = workerBehavior.poll(workerThread); // Do not EVER block a GUI display thread!!! if (tmpimg != null) { currentImage = tmpimg.getImage() ; isTimeToRead = false ; } } if (currentImage != null) { image(currentImage, width/2, height/2); // display centered in display window } } void keyPressed() { // Called an non-draw() time when key press event arrives if (key == 'n') { // 'n' for nect image isTimeToRead = true ; } } @Immutable class PImageContainer { // PImageContainer is here just to illustrate an @Immutable class. // I have used such "dumb data containers" to hold multiple objects // of the same or differing types such as PShape for a vector graphic. // Note how it is necessary to return a copy of the mutable PImage object // on getImage() in order to maintain effective immutability of this // object. private final PImage img ; // init final fields here or in constructor, NOT both public PImageContainer(PImage img) { this.img = img ; // parameter is unqualified "img" } public PImage getImage() { return img.copy() ; } } @ThreadSafe class FileReaderActiveClass implements java.lang.Runnable { /* CSC543 STUDENTS: Why is this class thread-safe? 1. All of its data fields are private and will not leak out of this class unless published by a non-private method (member function). 2. Any field that can be declared final is so declared. This makes that field immutable & also guarantees flush to main memory before accesses to objects of this class. It may point to mutable objects; filenames does, but that mutable object is thread-safe. 3. Non-final fields are declared volatile because the constructor is called from setup() and runs in a thread different from the run() thread explicitly started in setup(). 4. We could have non-final and non-mutable data fields be thread-safe IF every access to them is @GuardedBy an explicit lock (e.g., ReentrantLock) or a synchronized section on a common object (@GuardedBy that object). */ // FEB 13 polledPimageContainer atomic is safe but non-blocking, use interrupts top block sleep private final AtomicReference polledPimageContainer = new AtomicReference(); private volatile boolean interruptMe = false ; // clunky private final List filenames ; // MOVED TO IN-THREAD CONFINEMENT private volatile int fileindex = -1 ; private final String sp = sketchPath(""); public FileReaderActiveClass() throws FileNotFoundException { Scanner fileNameReader = new Scanner(new File(sp + "/photolist.txt")); int filecount = 0 ; LinkedList notThreadSafeLocal = new LinkedList(); // LinkedList avoids CopyOnWriteArrayList overhead but is not thread safe, thread confined while (fileNameReader.hasNextLine()) { String fname = fileNameReader.nextLine().trim(); if (fname.length() > 0 && ! (fname.startsWith("#") || fname.startsWith("//"))) { notThreadSafeLocal.add(fname); filecount++ ; } } fileNameReader.close(); // if (notThreadSafeLocal.size() > 0) { if (filecount == 0) { // fileindex = 0 ; throw new FileNotFoundException("ERROR, No image files found in " + sp + "/photolist.txt"); } filenames = new CopyOnWriteArrayList(notThreadSafeLocal); // avoid mutate overhead } PImageContainer poll(Thread workerTh) { // Called from client, thread-psafe lock. PImageContainer result = null ; result = polledPimageContainer.getAndSet(null); if (interruptMe) { workerTh.interrupt(); // in case it is sleeping, rude and not best solution } return result ; } public void run() { int fileindex = 0 ; // moved here from object field for thread confinement final float screenAspect = float(width) / float(height); println("FileReaderActiveClass.run()'s thread is " + Thread.currentThread()); while (true) { try { String nm = filenames.get(fileindex); //println("DEBUG ABOUT TO READ", nm); PImage thing = loadImage(sp + "/" + nm) ; float thingAspect = float(thing.width) / float(thing.height); if (screenAspect > thingAspect) { float multiplier = float(height) / float(thing.height); thing.resize(round(thing.width*multiplier),height); } else if (screenAspect < thingAspect) { float multiplier = float(width) / float(thing.width); thing.resize(width, round(thing.height*multiplier)); } else { thing.resize(width, height); // DEBUG } PImageContainer carrier = new PImageContainer(thing); while (! polledPimageContainer.compareAndSet(null,carrier)) { try { interruptMe = true ; Thread.sleep(10000); // block 10 seconds until it becomes null } catch (InterruptedException iex) { // Just loop back up & keep waiting. Interrupts not used in this sketch. } finally { interruptMe = false ; } } // PGraphics pg = createGraphics(width, height); // Uses display thread, blows up!!! // pg.background(0); fileindex = (fileindex+1) % filenames.size() ; // println("DEBUG THREAD RECVD ", nm); // } catch (InterruptedException iex) { // println("InterruptedException: " + iex.getMessage()); } catch (Exception xxx) { println("Image File Reading Exception on '" + filenames.get(fileindex) + "': " + xxx.getMessage()); exit(); fileindex = (fileindex+1) % filenames.size() ; } } } }