// 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 org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; /** * 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(); } }