blob: ff981c703ded3b5d9c84fc3be3f2ac4416169731 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.concurrent;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ConditionallyThreadCompatible;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ConditionallyThreadSafe;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* This file just contains some examples of the use of
* annotations for different categories of thread safety:
* ThreadSafe
* ThreadCompatible
* ThreadHostile
* Immutable ThreadSafe
* Immutable ThreadHostile
*
* It doesn't really test much -- just that this code
* using those annotations compiles and runs.
*
* The main class here is annotated as being both ConditionallyThreadSafe
* and ConditionallyThreadCompatible, and accordingly we document here the
* conditions under which it is thread-safe and thread-compatible:
* - it is thread-safe if you only use the testThreadSafety() method,
* the ThreadSafeCounter class, and/or ImmutableThreadSafeCounter class;
* - it is thread-compatible if you use only those and/or the
* ThreadCompatibleCounter and/or ImmutableThreadCompatibleCounter class;
* - it is thread-hostile otherwise.
*/
@ConditionallyThreadSafe @ConditionallyThreadCompatible
@RunWith(JUnit4.class)
public class ThreadSafetyTest {
@ThreadSafe
public static final class ThreadSafeCounter {
// A ThreadSafe class can have public mutable fields,
// provided they are atomic or volatile.
public volatile boolean myBool;
public AtomicInteger myInt;
// A ThreadSafe class can have private mutable fields,
// provided that access to them is synchronized.
private int value;
public ThreadSafeCounter(int value) {
synchronized (this) { // is this needed?
this.value = value;
}
}
public synchronized int getValue() {
return value;
}
public synchronized void increment() {
value++;
}
// A ThreadSafe class can have private mutable members
// provided that the methods of the class synchronize access
// to them.
// These members could be static...
private static int numFoos = 0;
public static synchronized void foo() {
numFoos++;
}
public static synchronized int getNumFoos() {
return numFoos;
}
// ... or non-static.
private int numBars = 0;
public synchronized void bar() {
numBars++;
}
public synchronized int getNumBars() {
return numBars;
}
}
@ThreadCompatible
public static final class ThreadCompatibleCounter {
// A ThreadCompatible class can have public mutable fields.
public int value;
public ThreadCompatibleCounter(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void increment() {
value++;
}
// A ThreadCompatible class can have mutable static members
// provided that the methods of the class synchronize access
// to them.
private static int numFoos = 0;
public static synchronized void foo() {
numFoos++;
}
public static synchronized int getNumFoos() {
return numFoos;
}
}
@ThreadHostile
public static final class ThreadHostileCounter {
// A ThreadHostile class can have public mutable fields.
public int value;
public ThreadHostileCounter(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void increment() {
value++;
}
// A ThreadHostile class can perform unsynchronized access
// to mutable static data.
private static int numFoos = 0;
public static void foo() {
numFoos++;
}
public static int getNumFoos() {
return numFoos;
}
}
@Immutable @ThreadSafe
public static final class ImmutableThreadSafeCounter {
// An Immutable ThreadSafe class can have public fields,
// provided they are final and immutable.
public final int value;
public ImmutableThreadSafeCounter(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public ImmutableThreadSafeCounter increment() {
return new ImmutableThreadSafeCounter(value + 1);
}
// An Immutable ThreadSafe class can have immutable static members.
public static final int NUM_STATIC_CACHE_ENTRIES = 3;
private static final ImmutableThreadSafeCounter[] staticCache =
new ImmutableThreadSafeCounter[] {
new ImmutableThreadSafeCounter(0),
new ImmutableThreadSafeCounter(1),
new ImmutableThreadSafeCounter(2)
};
public static ImmutableThreadSafeCounter makeUsingStaticCache(int value) {
if (value < NUM_STATIC_CACHE_ENTRIES) {
return staticCache[value];
} else {
return new ImmutableThreadSafeCounter(value);
}
}
// An Immutable ThreadSafe class can have private mutable members
// provided that the methods of the class synchronize access
// to them.
// These members could be static...
private static int cachedValue = 0;
private static ImmutableThreadSafeCounter cachedCounter =
new ImmutableThreadSafeCounter(0);
public static synchronized ImmutableThreadSafeCounter
makeUsingDynamicCache(int value) {
if (value != cachedValue) {
cachedValue = value;
cachedCounter = new ImmutableThreadSafeCounter(value);
}
return cachedCounter;
}
// ... or non-static.
private ImmutableThreadSafeCounter incrementCache = null;
public synchronized ImmutableThreadSafeCounter incrementUsingCache() {
if (incrementCache == null) {
incrementCache = new ImmutableThreadSafeCounter(value + 1);
}
return incrementCache;
}
// Methods of an Immutable class need not be deterministic.
private static Random random = new Random();
public int choose() {
return random.nextInt(value);
}
}
@Immutable @ThreadHostile
public static final class ImmutableThreadHostileCounter {
// An Immutable ThreadHostile class can have public fields,
// provided they are final and immutable.
public final int value;
public ImmutableThreadHostileCounter(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public ImmutableThreadHostileCounter increment() {
return new ImmutableThreadHostileCounter(value + 1);
}
// An Immutable ThreadHostile class can have private mutable members,
// and doesn't need to synchronize access to them.
// These members could be static...
private static int cachedValue = 0;
private static ImmutableThreadHostileCounter cachedCounter =
new ImmutableThreadHostileCounter(0);
public static ImmutableThreadHostileCounter
makeUsingDynamicCache(int value) {
if (value != cachedValue) {
cachedValue = value;
cachedCounter = new ImmutableThreadHostileCounter(value);
}
return cachedCounter;
}
// ... or non-static.
private ImmutableThreadHostileCounter incrementCache = null;
public ImmutableThreadHostileCounter incrementUsingCache() {
if (incrementCache == null) {
incrementCache = new ImmutableThreadHostileCounter(value + 1);
}
return incrementCache;
}
}
@Test
public void threadSafety() throws InterruptedException {
final ThreadSafeCounter[] threadSafeCounterArray =
new ThreadSafeCounter[] {
new ThreadSafeCounter(1), new ThreadSafeCounter(2), new ThreadSafeCounter(3)
};
final ThreadCompatibleCounter[] threadCompatibleCounterArray =
new ThreadCompatibleCounter[] {
new ThreadCompatibleCounter(1),
new ThreadCompatibleCounter(2),
new ThreadCompatibleCounter(3)
};
final ThreadHostileCounter threadHostileCounter =
new ThreadHostileCounter(1);
class MyThread implements Runnable {
ThreadCompatibleCounter threadCompatibleCounter =
new ThreadCompatibleCounter(1);
@Override
public void run() {
// ThreadSafe objects can be accessed with without synchronization
for (ThreadSafeCounter counter : threadSafeCounterArray) {
counter.increment();
}
// ThreadCompatible objects can be accessed with without
// synchronization if they are thread-local
threadCompatibleCounter.increment();
// Access to ThreadCompatible objects must be synchronized
// if they could be concurrently accessed by other threads
for (ThreadCompatibleCounter counter : threadCompatibleCounterArray) {
synchronized (counter) {
counter.increment();
}
}
// Access to ThreadHostile objects must be synchronized.
synchronized (this.getClass()) {
threadHostileCounter.increment();
}
}
}
Thread thread1 = new Thread(new MyThread());
Thread thread2 = new Thread(new MyThread());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}