0

Feel free to correct me if I am wrong!

The synchronized keyword in java makes a method unable to be run be different threads simultaneously. In my program I have 4 different threads that run on the same time counting to 100.000.

When adding the synchronized keyword to the method being performed, it should take four times the amount of time as it would multithreading?

Executing the programs either way, takes roughly 16 seconds.

Heres my code!

public class ExerciseThree {

    public static void main(String[] args) {
        Even even = new Even();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                System.out.println(even.next());
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                System.out.println(even.next());
            }
        });
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                System.out.println(even.next());
            }
        });
        Thread t4 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                System.out.println(even.next());
            }
        });
        System.out.println("starting thread 1");
        t1.start();
        System.out.println("starting thread 2");
        t2.start();
        System.out.println("starting thread 3");
        t3.start();
        System.out.println("starting thread 4");
        t4.start();
    }
}

The method being called by the threads

public class Even {

        private int n = 0;

//      public synchronized int next() {
        public int next() {
            n++;
            n++;
            return n;
        }
    }
Jonas Grønbek
  • 1,709
  • 2
  • 22
  • 49
  • 1
    There will probably some quick and not-so-appropriate answers to this. As a starter: https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java/4480774 . Beyond that, things are *far* more complicated in reality than on paper. The JVM with its internal optimizers is a beast, and you cannot even guess at which point how much time is spent in this program... – Marco13 Aug 20 '18 at 19:10
  • 3
    One problem you have here: the println() is probably hundreds or *thousands* of times slower than the call to next(). That alone can lead to all kinds of strange effects. You definitely should study https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java ... otherwise your measurements are close to meaningless. – GhostCat Aug 20 '18 at 19:10
  • 3
    @GhostCat Regarding my statement "...you cannot even guess...", I have to say: The `println`s are a good guess when it is about where *most* of the time is spent ;-) – Marco13 Aug 20 '18 at 19:14
  • Thanks a ton guys, I would mark as an answer but you haven't submitted. I tried to remove the System.out.println(); and made the loops last longer and the synchronized came out with 36 seconds and without 16. My faith in multithreading now persists! – Jonas Grønbek Aug 20 '18 at 19:17

2 Answers2

1

With println being much more expensive than the computation, it's all about concurrent execution of it. However, println itself is synchronized, so there can be no speed up.

Without it, doing just

public int next() {
    n++;
    n++;
    return n;
}

is subject to many optimizations. Especially the double increment can be replaced by n+=2 and the return gets eliminated as the returned value doesn't get used. A loop like

for (int i = 0; i < 100000; i++) {
    even.next());
}

can be reduced to just n += 200000.


Benchnmarking is hard in general and especially in Java. By all means, use JMH, which takes care of most problems.

maaartinus
  • 44,714
  • 32
  • 161
  • 320
1

As already pointed out in the comment section, microbenchmarking is a complex matter as many factors influence the execution time (e.g., just-in-time compilation and garbage collection). A good reference was already provided in the comments section, but I suggest that you also take a look at my answer for a similar question which links to an external resource by Peter Sestoft that provides a very good introduction to microbenchmarking and what one needs to be aware of.

It has already been mentioned that println() has no place in a microbenchmark like this. In addition, I'd like to point out that you should use some sort of synchronization mechanism (e.g., a CountDownLatch) to make sure that the four threads start performing their work at the same time. The overhead involved in creating and starting the threads may result in the earlier threads getting a headstart on their work during the time it takes for the later ones to start, thereby creating less contention for the even lock than what you expect. This could for example look something like this:

public class ExerciseThree {

    public static void main(String[] args) {
        final CountDownLatch startSignal = new CountDownLatch(1);
        final CountDownLatch threadReadyCheck = new CountDownLatch(4);
        final CountDownLatch threadDoneCheck = new CountDownLatch(4);
        Even even = new Even();
        Thread t1 = new Thread(() -> {
            threadReadyCheck.countDown();
            startSignal.await();
            for (int i = 0; i < 100000; i++) {
                even.next();
            }
            threadDoneCheck.countDown();
        });
        Thread t2 = new Thread(() -> {
            threadReadyCheck.countDown();
            startSignal.await();
            for (int i = 0; i < 100000; i++) {
                even.next();
            }
            threadDoneCheck.countDown();
        });
        Thread t3 = new Thread(() -> {
            threadReadyCheck.countDown();
            startSignal.await();
            for (int i = 0; i < 100000; i++) {
                even.next();
            }
            threadDoneCheck.countDown();
        });
        Thread t4 = new Thread(() -> {
            threadReadyCheck.countDown();
            startSignal.await();
            for (int i = 0; i < 100000; i++) {
                even.next();
            }
            threadDoneCheck.countDown();
        });
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        // Wait until all threads are ready to perform their work.
        threadReadyCheck.await();
        // All threads ready.
        // This is where you log start time.
        long start = System.nanoTime();
        // Let threads progress to perform their actual work.
        startSignal.countDown();
        // Wait for threads to finish their work.
        threadDoneCheck.await();
        long end = System.nanoTime();
        // Note that this is again subject to many factors, for example when the main thread gets scheduled again after the workers terminate.
        long executionTime = end - start;
    }
}
Janus Varmarken
  • 2,306
  • 3
  • 20
  • 42