# HG changeset patch # User Jaroslav Tulach # Date 1234287381 -3600 # Node ID 08dd52950883b13f6462fd95952369f1808be81d # Parent 03a451fc2256219e9471ce24652ef7e951a82d3b Example for the "java monitor pitfalls" chapter diff -r 03a451fc2256 -r 08dd52950883 samples/deadlock/src/org/apidesign/javamonitorflaws/Cache.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/deadlock/src/org/apidesign/javamonitorflaws/Cache.java Tue Feb 10 18:36:21 2009 +0100 @@ -0,0 +1,40 @@ +package org.apidesign.javamonitorflaws; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Jaroslav Tulach + */ +public abstract class Cache { + private Map cache; + + public final To get(From f) { + for (;;) { + synchronized (this) { + if (cache == null) { + cache = new HashMap(); + } + To t = cache.get(f); + if (t != null) { + return t; + } + } + + To newT = createItem(f); + + synchronized (this) { + To t = cache.get(f); + if (t == null) { + cache.put(f, newT); + return newT; + } else { + return t; + } + } + } + } + + protected abstract To createItem(From f); +} diff -r 03a451fc2256 -r 08dd52950883 samples/deadlock/test/org/apidesign/javamonitorflaws/CacheTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/deadlock/test/org/apidesign/javamonitorflaws/CacheTest.java Tue Feb 10 18:36:21 2009 +0100 @@ -0,0 +1,131 @@ +package org.apidesign.javamonitorflaws; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import org.netbeans.junit.NbTestCase; + +public class CacheTest extends NbTestCase { + private MultiplyCache cache = new MultiplyCache(); + + public CacheTest(String n) { + super(n); + } + + @Override + protected int timeOut() { + return 2000; + } + + /** + * To simulate deadlock between the cache's setMultiply method and + * ToDeadlock's own lock. The root cause is that setMultiply is calling + * foreign code while holding the caches's this lock: + +Thread Test Watch Dog: testDeadlockWithSetter + org.apidesign.javamonitorflaws.MultiplyCache.setMultiply:19 + org.apidesign.javamonitorflaws.CacheTest$1ToDeadlock.assertMultiplyByTen:40 + org.apidesign.javamonitorflaws.CacheTest.testDeadlockWithSetter:50 + sun.reflect.NativeMethodAccessorImpl.invoke0:-2 + sun.reflect.NativeMethodAccessorImpl.invoke:39 + sun.reflect.DelegatingMethodAccessorImpl.invoke:25 + java.lang.reflect.Method.invoke:597 + junit.framework.TestCase.runTest:168 + org.netbeans.junit.NbTestCase.access$200:84 + org.netbeans.junit.NbTestCase$2.doSomething:328 + org.netbeans.junit.NbTestCase$1Guard.run:265 + java.lang.Thread.run:619 +Thread to deadlock + org.apidesign.javamonitorflaws.CacheTest$1ToDeadlock.assertMultiplyByTen:40 + org.apidesign.javamonitorflaws.CacheTest$1ToDeadlock.propertyChange:36 + java.beans.PropertyChangeSupport.firePropertyChange:339 + java.beans.PropertyChangeSupport.firePropertyChange:276 + java.beans.PropertyChangeSupport.firePropertyChange:297 + org.apidesign.javamonitorflaws.MultiplyCache.setMultiply:21 + org.apidesign.javamonitorflaws.CacheTest$1ToDeadlock.run:28 + java.lang.Thread.run:619 + + */ + public void testDeadlockWithSetter() throws Exception { + if (Boolean.getBoolean("no.failures")) return; + + class ToDeadlock implements Runnable, PropertyChangeListener { + int value; + + public void run() { + cache.setMultiply(10); + } + public void propertyChange(PropertyChangeEvent evt) { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + // ok + } + changeMultiplyToSeven(); + } + + public synchronized void changeMultiplyToSeven() { + cache.setMultiply(7); + } + } + ToDeadlock toDeadlock = new ToDeadlock(); + cache.addPropertyChangeListener(toDeadlock); + Thread t = new Thread(toDeadlock, "to deadlock"); + t.start(); + + Thread.sleep(100); + + toDeadlock.changeMultiplyToSeven(); + } + + /** Shows that one can deadlock with the cache's API even the API + * is locally correctly synchronized. +Thread Test Watch Dog: testDeadlockWithTheAPICacheItself + org.apidesign.javamonitorflaws.Cache.get:16 + org.apidesign.javamonitorflaws.CacheTest$2ToDeadlock.assertMultiplyByTen + org.apidesign.javamonitorflaws.CacheTest.testDeadlockViaAPI:112 + java.lang.reflect.Method.invoke:597 + org.netbeans.junit.NbTestCase.access$200:84 + org.netbeans.junit.NbTestCase$2.doSomething:328 + org.netbeans.junit.NbTestCase$1Guard.run:265 + java.lang.Thread.run:619 +Thread Deadlock using API + org.apidesign.javamonitorflaws.CacheTest$2ToDeadlock.assertMultiplyByTen + org.apidesign.javamonitorflaws.CacheTest$2ToDeadlock.propertyChange:98 + java.beans.PropertyChangeSupport.firePropertyChange:339 + java.beans.PropertyChangeSupport.firePropertyChange:276 + java.beans.PropertyChangeSupport.firePropertyChange:297 + org.apidesign.javamonitorflaws.MultiplyCache.setMultiply:21 + org.apidesign.javamonitorflaws.CacheTest$2ToDeadlock.run:90 + java.lang.Thread.run:619 + */ + public void testDeadlockViaAPI() throws Exception { + if (Boolean.getBoolean("no.failures")) return; + + class ToDeadlock implements Runnable, PropertyChangeListener { + public void run() { + cache.setMultiply(10); + } + public void propertyChange(PropertyChangeEvent evt) { + try { + Thread.sleep(500); + } catch (InterruptedException ex) { + // ok + } + assertMultiplyByTen(); + } + + public synchronized void assertMultiplyByTen() { + int value = cache.get("123"); + assertEquals("3*10=30", 30, value); + } + } + ToDeadlock toDeadlock = new ToDeadlock(); + cache.addPropertyChangeListener(toDeadlock); + Thread t = new Thread(toDeadlock, "Deadlock using API"); + t.start(); + + Thread.sleep(100); + + toDeadlock.assertMultiplyByTen(); + } +} \ No newline at end of file diff -r 03a451fc2256 -r 08dd52950883 samples/deadlock/test/org/apidesign/javamonitorflaws/MultiplyCache.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/deadlock/test/org/apidesign/javamonitorflaws/MultiplyCache.java Tue Feb 10 18:36:21 2009 +0100 @@ -0,0 +1,43 @@ +package org.apidesign.javamonitorflaws; + +import org.apidesign.javamonitorflaws.Cache; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +/** + * + * @author Jaroslav Tulach + */ +public class MultiplyCache extends Cache { + private PropertyChangeSupport pcs; + private int multiply; + public static final String PROP_MULTIPLY = "multiply"; + + public synchronized int getMultiply() { + return multiply; + } + public synchronized void setMultiply(int multiply) { + int oldMultiply = this.multiply; + this.multiply = multiply; + pcs.firePropertyChange(PROP_MULTIPLY, oldMultiply, multiply); + } + + public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { + if (pcs == null) { + pcs = new PropertyChangeSupport(this); + } + pcs.addPropertyChangeListener(listener); + } + public void removePropertyChangeListener(PropertyChangeListener listener) { + if (pcs != null) { + pcs.removePropertyChangeListener(listener); + } + } + + @Override + protected Integer createItem(String f) { + return f.length() * getMultiply(); + } + + +} \ No newline at end of file