1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 import java.util.concurrent.atomic.AtomicInteger; 17 18 public class Main { 19 20 private final boolean PRINT_TIMES = false; // False for use as run test. 21 22 // Number of increments done by each thread. Must be multiple of largest hold time below, 23 // times any possible thread count. Finishes much faster when used as run test. 24 private final int TOTAL_ITERS = PRINT_TIMES? 16_000_000 : 1_600_000; 25 private final int MAX_HOLD_TIME = PRINT_TIMES? 2_000_000 : 200_000; 26 27 private int counter; 28 29 private AtomicInteger atomicCounter = new AtomicInteger(); 30 31 private Object lock; 32 33 private int currentThreadCount = 0; 34 35 // A function such that if we repeatedly apply it to -1, the value oscillates 36 // between -1 and 3. Thus the average value is 1. 37 // This is designed to make it hard for the compiler to predict the values in 38 // the sequence. nextInt(int x)39 private int nextInt(int x) { 40 if (x < 0) { 41 return x * x + 2; 42 } else { 43 return x - 4; 44 } 45 } 46 47 // Increment counter by n, holding log for time roughly propertional to n. 48 // N must be even. holdFor(Object lock, int n)49 private void holdFor(Object lock, int n) { 50 synchronized(lock) { 51 int y = -1; 52 for (int i = 0; i < n; ++i) { 53 counter += y; 54 y = nextInt(y); 55 } 56 } 57 } 58 59 private class RepeatedLockHolder implements Runnable { RepeatedLockHolder(boolean shared, int n )60 RepeatedLockHolder(boolean shared, int n /* even */) { 61 sharedLock = shared; 62 holdTime = n; 63 } 64 @Override run()65 public void run() { 66 Object myLock = sharedLock ? lock : new Object(); 67 int nIters = TOTAL_ITERS / currentThreadCount / holdTime; 68 for (int i = 0; i < nIters; ++i) { 69 holdFor(myLock, holdTime); 70 } 71 } 72 private boolean sharedLock; 73 private int holdTime; 74 } 75 76 private class SleepyLockHolder implements Runnable { SleepyLockHolder(boolean shared)77 SleepyLockHolder(boolean shared) { 78 sharedLock = shared; 79 } 80 @Override run()81 public void run() { 82 Object myLock = sharedLock ? lock : new Object(); 83 int nIters = TOTAL_ITERS / currentThreadCount / 10_000; 84 for (int i = 0; i < nIters; ++i) { 85 synchronized(myLock) { 86 try { 87 Thread.sleep(2); 88 } catch(InterruptedException e) { 89 throw new AssertionError("Unexpected interrupt"); 90 } 91 counter += 10_000; 92 } 93 } 94 } 95 private boolean sharedLock; 96 } 97 98 // Increment atomicCounter n times, on average by 1 each time. 99 private class RepeatedIncrementer implements Runnable { 100 @Override run()101 public void run() { 102 int y = -1; 103 int nIters = TOTAL_ITERS / currentThreadCount; 104 for (int i = 0; i < nIters; ++i) { 105 atomicCounter.addAndGet(y); 106 y = nextInt(y); 107 } 108 } 109 } 110 111 // Run n threads doing work. Return the elapsed time this took, in milliseconds. runMultiple(int n, Runnable work)112 private long runMultiple(int n, Runnable work) { 113 Thread[] threads = new Thread[n]; 114 // Replace lock, so that we start with a clean, uninflated lock each time. 115 lock = new Object(); 116 for (int i = 0; i < n; ++i) { 117 threads[i] = new Thread(work); 118 } 119 long startTime = System.currentTimeMillis(); 120 for (int i = 0; i < n; ++i) { 121 threads[i].start(); 122 } 123 for (int i = 0; i < n; ++i) { 124 try { 125 threads[i].join(); 126 } catch(InterruptedException e) { 127 throw new AssertionError("Unexpected interrupt"); 128 } 129 } 130 return System.currentTimeMillis() - startTime; 131 } 132 133 // Run on different numbers of threads. runAll(Runnable work, Runnable init, Runnable checker)134 private void runAll(Runnable work, Runnable init, Runnable checker) { 135 for (int i = 1; i <= 8; i *= 2) { 136 currentThreadCount = i; 137 init.run(); 138 long time = runMultiple(i, work); 139 if (PRINT_TIMES) { 140 System.out.print(time + (i == 8 ? "\n" : "\t")); 141 } 142 checker.run(); 143 } 144 } 145 146 private class CheckAtomicCounter implements Runnable { 147 @Override run()148 public void run() { 149 if (atomicCounter.get() != TOTAL_ITERS) { 150 throw new AssertionError("Failed atomicCounter postcondition check for " 151 + currentThreadCount + " threads"); 152 } 153 } 154 } 155 156 private class CheckCounter implements Runnable { 157 @Override run()158 public void run() { 159 if (counter != TOTAL_ITERS) { 160 throw new AssertionError("Failed counter postcondition check for " 161 + currentThreadCount + " threads"); 162 } 163 } 164 } 165 run()166 private void run() { 167 if (PRINT_TIMES) { 168 System.out.println("All times in milliseconds for 1, 2, 4 and 8 threads"); 169 } 170 System.out.println("Atomic increments"); 171 runAll(new RepeatedIncrementer(), () -> { atomicCounter.set(0); }, new CheckAtomicCounter()); 172 for (int i = 2; i <= MAX_HOLD_TIME; i *= 10) { 173 // i * 8 (max thread count) divides TOTAL_ITERS 174 System.out.println("Hold time " + i + ", shared lock"); 175 runAll(new RepeatedLockHolder(true, i), () -> { counter = 0; }, new CheckCounter()); 176 } 177 if (PRINT_TIMES) { 178 for (int i = 2; i <= MAX_HOLD_TIME; i *= 1000) { 179 // i divides TOTAL_ITERS 180 System.out.println("Hold time " + i + ", private lock"); 181 // Since there is no mutual exclusion final counter value is unpredictable. 182 runAll(new RepeatedLockHolder(false, i), () -> { counter = 0; }, () -> {}); 183 } 184 } 185 System.out.println("Hold for 2 msecs while sleeping, shared lock"); 186 runAll(new SleepyLockHolder(true), () -> { counter = 0; }, new CheckCounter()); 187 System.out.println("Hold for 2 msecs while sleeping, private lock"); 188 runAll(new SleepyLockHolder(false), () -> { counter = 0; }, () -> {}); 189 } 190 main(String[] args)191 public static void main(String[] args) { 192 System.out.println("Starting"); 193 new Main().run(); 194 } 195 } 196