• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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