| // 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(); |
| } |
| |
| } |