/** * The most Java way I could think of for implementing testing * of synchronized, atomic, locked, and unsync among shared memory * threads. * * A very simple program, just add to a single counter on * several threads all running at the same time. The point is * that the standard long will loose some counts along the way * because the threads will interact. This will not happen with * atomics and synchronized * * @author gtowell * Created Aug 2022 */ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ExecCounter { /** * An interface for several classes to implement. * The point of the interface is that it allows me to write * a single method to do the actual testing. Is it a win?? */ private interface MCounter { // increment an internal counter void increment(); // get the value of the internal counter long value(); } /** * Synchronized addition and get * Because the increment and value functions are independently * synchro, they could be executed simultaneously. */ private class SynchronizedCounter implements MCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized long value() { return c; } } /** * addition and get protected by reentrantlock * Note that by using one lock we get guarantee that * the counter is not being changed while it is being read. */ private class LockCounter implements MCounter { private int c = 0; Lock l = new ReentrantLock(); public void increment() { try{ l.lock(); c++; } finally { l.unlock(); } } public long value() { try{ l.lock(); return c; } finally { l.unlock(); } } } /** * Unsynchronized addition and get.. * UNSAFE! */ private class UnsynchronizedCounter implements MCounter { private int c = 0; public void increment() { c++; } public long value() { return c; } } /** * Atomic addition and get * Atomics are part of may languages. Generally much * faster than locks or synchronized. * */ private class AtomicCounter implements MCounter { private AtomicLong c = new AtomicLong(0);; public void increment() { c.incrementAndGet(); } public synchronized long value() { return c.get(); } } // A ThreadPool executor. Using this means that the total number of // threads in the system is capped. private ExecutorService exec = Executors.newFixedThreadPool(100); /** * Run a test. * @param mc An object that implements the MCounter interface * @param reps the number of increments to do in a single thread * @param trials the number of threads to start */ public void testt(MCounter mc, long reps, long trials) { for (long i = 0; i < trials; i++) { exec.execute(new Thread("" + i) { public void run() { System.err.println("Start " + this); for (long j = 0; j < reps; j++) mc.increment(); } }); } } public static void main(String[] args) { ExecCounter cc = new ExecCounter(); cc.test(args.length>0); } /** * Run the test * @param waitForCompletion force the program to * threads to finish */ public void test(boolean waitForCompletion) { long incc = 1000000; long reps = 10; UnsynchronizedCounter uc = new UnsynchronizedCounter(); SynchronizedCounter sc = new SynchronizedCounter(); AtomicCounter ac = new AtomicCounter(); LockCounter lc = new LockCounter(); testt(uc, incc, reps); testt(sc, incc, reps); testt(ac, incc, reps); testt(lc, incc, reps); if (waitForCompletion) { try { Thread.sleep(1000); exec.shutdown(); exec.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); return; } } System.err.format("Long %d\n", uc.value()); System.err.format("Atomic %d\n", ac.value()); System.err.format("Locked %d\n", lc.value()); System.err.format("Synchr %d\n", sc.value()); } }