import de.example.sql.deadlocks.example.FirstTransaction;
import de.example.sql.deadlocks.example.SecondTransaction;
+import de.example.sql.deadlocks.gate.ThreadGate;
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
-
public static void main(String[] args) {
+ final ThreadGate trx1Gate = new ThreadGate();
+ final ThreadGate trx2Gate = new ThreadGate();
+
logger.info("Starting application");
final FutureTask<Void> taskFirst = new FutureTask<Void>
@Override
public void run() {
final FirstTransaction first = (FirstTransaction) SpringContextLocator.getInstance().getBean("firstTransaction");
- first.doTransaction();
+ first.setThreadGateTrx1(trx1Gate);
+ first.setThreadGateTrx2(trx2Gate);
+ first.doFirstStep();
}
},
null
);
- new Thread(taskFirst).start();
-
final FutureTask<Void> taskSecond = new FutureTask<Void>
(
new Runnable(){
@Override
public void run() {
final SecondTransaction second = (SecondTransaction) SpringContextLocator.getInstance().getBean("secondTransaction");
- second.doTransaction();
+ second.setThreadGateTrx1(trx1Gate);
+ second.setThreadGateTrx2(trx2Gate);
+ second.doSecondStep();
}
},
null
);
+
+ new Thread(taskFirst).start();
new Thread(taskSecond).start();
// Wait for end.
package de.example.sql.deadlocks.annotation;
import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
+@Inherited
public @interface DeadlockRetry {
/**
--- /dev/null
+package de.example.sql.deadlocks.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Retention;
+
+/*
+ * Copyright (c) 2005 Brian Goetz
+ * Released under the Creative Commons Attribution License
+ * (http://creativecommons.org/licenses/by/2.5)
+ * Official home: http://www.jcip.net
+ */
+
+/**
+ * GuardedBy
+ *
+ * The field or method to which this annotation is applied can only be accessed
+ * when holding a particular lock, which may be a built-in (synchronization)
+ * lock, or may be an explicit java.util.concurrent.Lock.
+ *
+ * The argument determines which lock guards the annotated field or method: this :
+ * The string literal "this" means that this field is guarded by the class in
+ * which it is defined. class-name.this : For inner classes, it may be necessary
+ * to disambiguate 'this'; the class-name.this designation allows you to specify
+ * which 'this' reference is intended itself : For reference fields only; the
+ * object to which the field refers. field-name : The lock object is referenced
+ * by the (instance or static) field specified by field-name.
+ * class-name.field-name : The lock object is reference by the static field
+ * specified by class-name.field-name. method-name() : The lock object is
+ * returned by calling the named nil-ary method. class-name.class : The Class
+ * object for the specified class should be used as the lock object.
+ */
+@Target( { ElementType.FIELD, ElementType.METHOD })
+@Retention(RetentionPolicy.CLASS)
+public @interface GuardedBy {
+ String value();
+}
--- /dev/null
+package de.example.sql.deadlocks.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * ThreadSafe
+ *
+ * The class to which this annotation is applied is thread-safe. This means that
+ * no sequences of accesses (reads and writes to public fields, calls to public
+ * methods) may put the object into an invalid state, regardless of the
+ * interleaving of those actions by the runtime, and without requiring any
+ * additional synchronization or coordination on the part of the caller.
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ThreadSafe {
+}
import org.springframework.transaction.annotation.Transactional;
import de.example.sql.deadlocks.annotation.DeadlockRetry;
+import de.example.sql.deadlocks.gate.ThreadGate;
@Transactional
public class FirstTransaction {
private static final Logger logger = LoggerFactory.getLogger(FirstTransaction.class);
private DataSource dataSource;
+ private ThreadGate trx2Gate;
+ private ThreadGate trx1Gate;
+ private boolean isFirstTry = true;
@DeadlockRetry(maxTries = 10, interval = 5000)
- public void doTransaction() {
- logger.info("Running doTransaction");
+ public void doFirstStep() {
+ if (isFirstTry) {
+ isFirstTry = false;
+ this.doFirstStepWithGate();
+ } else {
+ // Retry after roll back.
+ this.doFirstStepWithoutGate();
+ }
+ }
+ public void doFirstStepWithGate() {
+ logger.info("Start doFirstStepWithGate");
+
+ logger.info("doFirstStepWithGate UPDATING");
final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource);
- jdbcTemplate.execute("UPDATE children SET name='Bilbo', parent_id='2' WHERE id='1'");
jdbcTemplate.execute("UPDATE parents SET name='Smith' WHERE id='1'");
+ jdbcTemplate.execute("SELECT * FROM children WHERE id='1' LOCK IN SHARE MODE");
- try {
- Thread.sleep(100000);
- } catch (final InterruptedException e) {
- logger.warn("First transaction thread interrupt");
+ trx2Gate.open();
- // Restore interrupt status.
- Thread.currentThread().interrupt();
- }
+ try {
+ this.trx1Gate.await();
+ } catch (final InterruptedException e) {
+ logger.warn("interrupt error", e);
+
+ Thread.currentThread().interrupt();
+ }
+ this.trx1Gate.close();
+
+ trx2Gate.open();
+
+ this.doThirdStep();
- logger.info("Running endTransaction");
+ logger.info("End doFirstStepWithGate");
+ }
+
+ public void doFirstStepWithoutGate() {
+ logger.info("Start doFirstStepWithoutGate");
+
+ logger.info("doFirstStepWithoutGate UPDATING");
+ final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource);
+ jdbcTemplate.execute("UPDATE parents SET name='Smith' WHERE id='1'");
+ jdbcTemplate.execute("SELECT * FROM children WHERE id='1' LOCK IN SHARE MODE");
+
+ this.doThirdStep();
+
+ logger.info("End doFirstStepWithoutGate");
+ }
+
+
+ private void doThirdStep() {
+ logger.info("Start doThirdStep");
+
+ logger.info("doThirdStep UPDATING");
+ final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource);
+ jdbcTemplate.execute("UPDATE children SET name='Bob', parent_id='1' WHERE id='2'");
+
+ // trx2 continues (fourth step)
+
+ logger.info("End doThirdStep");
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
+
+ public void setThreadGateTrx1(final ThreadGate trx1Gate) {
+ this.trx1Gate = trx1Gate;
+ }
+
+ public void setThreadGateTrx2(final ThreadGate trx2Gate) {
+ this.trx2Gate = trx2Gate;
+ }
}
import org.springframework.transaction.annotation.Transactional;
import de.example.sql.deadlocks.annotation.DeadlockRetry;
+import de.example.sql.deadlocks.gate.ThreadGate;
@Transactional
public class SecondTransaction {
private static final Logger logger = LoggerFactory.getLogger(SecondTransaction.class);
private DataSource dataSource;
+ private ThreadGate trx2Gate;
+ private ThreadGate trx1Gate;
+ private boolean isFirstTry = true;
@DeadlockRetry(maxTries = 10, interval = 5000)
- public void doTransaction() {
- logger.info("Running doTransaction");
+ public void doSecondStep() {
+ if (isFirstTry) {
+ isFirstTry = false;
+ this.doSecondStepWithGate();
+ } else {
+ // Retry after roll back.
+ this.doSecondStepWithoutGate();
+ }
+ }
+
+ private void doSecondStepWithGate() {
+ logger.info("Start doSecondStepWithGate");
+
+ try {
+ this.trx2Gate.await();
+ } catch (InterruptedException e) {
+ logger.warn("interrupt error", e);
+
+ Thread.currentThread().interrupt();
+ }
+ this.trx2Gate.close();
+
+ logger.info("doSecondStepWithGate UPDATING");
+ final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource);
+ jdbcTemplate.execute("UPDATE parents SET name='Frodo' WHERE id='2'");
+ jdbcTemplate.execute("SELECT * FROM children WHERE id='2' LOCK IN SHARE MODE");
+
+ // trx1 continue
+ trx1Gate.open();
+
+ try {
+ this.trx2Gate.await();
+ } catch (InterruptedException e) {
+ logger.warn("interrupt error", e);
+
+ Thread.currentThread().interrupt();
+ }
+ this.trx2Gate.close();
+
+ this.doFourthStep();
+
+ logger.info("End doSecondStepWithGate");
+ }
+
+ private void doSecondStepWithoutGate() {
+ logger.info("Start doSecondStepWithoutGate");
+
+ logger.info("doSecondStepWithoutGate UPDATING");
+ final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource);
+ jdbcTemplate.execute("UPDATE parents SET name='Frodo' WHERE id='2'");
+ jdbcTemplate.execute("SELECT * FROM children WHERE id='2' LOCK IN SHARE MODE");
+
+ this.doFourthStep();
+
+ logger.info("End doSecondStepWithoutGate");
+ }
+
+ private void doFourthStep() {
+ logger.info("Start doFourthStep");
+ logger.info("doFourthStep UPDATING");
final JdbcOperations jdbcTemplate = new JdbcTemplate(dataSource);
- jdbcTemplate.execute("UPDATE children SET name='Frodo', parent_id='2' WHERE id='2'");
- jdbcTemplate.execute("UPDATE parents SET name='Smith' WHERE id='1'");
+ jdbcTemplate.execute("UPDATE children SET name='Sauron', parent_id='2' WHERE id='1'");
- logger.info("Running endTransaction");
+ logger.info("End doFourthStep");
}
- public void setDataSource(DataSource dataSource) {
+ public void setDataSource(final DataSource dataSource) {
this.dataSource = dataSource;
- }
+ }
+
+ public void setThreadGateTrx1(final ThreadGate trx1Gate) {
+ this.trx1Gate = trx1Gate;
+ }
+
+ public void setThreadGateTrx2(final ThreadGate trx2Gate) {
+ this.trx2Gate = trx2Gate;
+ }
}
--- /dev/null
+package de.example.sql.deadlocks.gate;
+
+import de.example.sql.deadlocks.annotation.GuardedBy;
+import de.example.sql.deadlocks.annotation.ThreadSafe;
+
+
+
+/**
+ * See: §Java Concurrency in practice 14.2.6
+ * @author
+ *
+ */
+@ThreadSafe
+public class ThreadGate {
+ //CONDITION-PREDICATE: opened-since(n) (isOpen || generation>n)
+ @GuardedBy("this") private boolean isOpen;
+ @GuardedBy("this") private int generation;
+
+
+ public synchronized void close() {
+ isOpen = false;
+ }
+
+ public synchronized void open() {
+ ++generation;
+ isOpen = true;
+ notifyAll();
+ }
+
+ //BLOCKS-UNTIL: opened-since(generation on entry)
+ public synchronized void await() throws InterruptedException {
+ int arrivalGeneration = generation;
+ while (!isOpen && arrivalGeneration == generation)
+ wait();
+ }
+}
+