/** * CSC543DemoCollectionsSynchronized derived from PImageContainer 2/13/2023 * PImageContainer 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.* ; 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 Queue imageQueue = new ConcurrentLinkedQueue(); // final BlockingQueue imageQueue = new LinkedBlockingQueue(10); final List imageQueue = Collections.synchronizedList(new LinkedList()); // java.util's LinkedList & ArrayList do not support fixed capacity bounds, nonblocking. // 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 void setup() { // Processing's startup function, called once size(1920, 1080); frameRate(60); println("setup()'s thread is " + Thread.currentThread()); try { FileReaderActiveClass workerBehavior = new FileReaderActiveClass(); Thread workerThread = new Thread(workerBehavior); 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 PImageContainer tmpimg = null ; if (isTimeToRead) { try { tmpimg = imageQueue.remove(0); // Do not EVER block a GUI display thread!!! } catch (IndexOutOfBoundsException none) { tmpimg = null ; } 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). */ private final List filenames = new CopyOnWriteArrayList(); // 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 ; while (fileNameReader.hasNextLine()) { String fname = fileNameReader.nextLine().trim(); if (fname.length() > 0 && ! (fname.startsWith("#") || fname.startsWith("//"))) { filenames.add(fname); filecount++ ; } } fileNameReader.close(); // if (filenames.size() > 0) { if (filecount == 0) { // fileindex = 0 ; throw new FileNotFoundException("ERROR, No image files found in " + sp + "/photolist.txt"); } } 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); if ((! imageQueue.add(carrier)) || (imageQueue.size() == filenames.size())) { // imageQueue.add on some Queue types throws an exception when capacity is reaches // There is a race with the display thread reducing imageQueue.size(). System.err.println("FILE LIMIT SIZE ON A NON-BLOCKING QUEUE REACHED BY READER."); // Processing's println is probably thread safe but don't risk it. System.err is. return ; // terminate this thread instead of loading too many PImage photos into memory } // 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() ; } } } }