Я пытаюсь написать метод на базе SQL DB, который работает как ConcurrentMap.putIfAbsent, где «значение» служит ключом и «id» как значение. Основным ограничением этого метода является сохранение значения уникальным по всей таблице.Пытается понять oracle Уровень транзакции_SERIALIZABLE
Ниже приведен мой пример этого метода. Вызов sync.yield()
передает управление другой теме. Он был добавлен для достижения необходимого параллельного выполнения потока.
import java.sql.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
private static final String USER = "";
private static final String PASS = USER;
private static final String URL = "jdbc:oracle:thin:@192.168.100.160:1521:main";
private static final AtomicInteger id = new AtomicInteger();
private static final Sync sync = new Sync(1);
static Connection getConnection() throws Exception {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection c = DriverManager.getConnection(URL, USER, PASS);
c.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
return c;
}
static long putIfAbsent(String value) throws Exception {
Connection c = getConnection();
PreparedStatement checkSt = c.prepareStatement("SELECT id from test WHERE value = ?");
checkSt.setString(1, value);
ResultSet rs = checkSt.executeQuery();
if (rs.next())
return rs.getLong(1);
System.out.println(Thread.currentThread() + " did not find value");
sync.yield();
long id = getId();
System.out.println(Thread.currentThread() + " prepare to insert value with id " + id);
PreparedStatement updateSt = c.prepareStatement("INSERT INTO test VALUES (?, ?)");
updateSt.setLong(1, id);
updateSt.setString(2, value);
updateSt.executeQuery();
c.commit();
c.close();
return id;
}
public static void main(String[] args) {
Runnable r =() -> {
try {
System.out.println(Thread.currentThread() + " commit success and return id = " + putIfAbsent("val"));
sync.yield();
} catch (Exception e) {
e.printStackTrace();
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
static long getId() {
return id.incrementAndGet();
}
}
При запуске основного метода на пустом столе, я получаю этот консольный вывод:
Thread[Thread-0,5,main] did not find value
Thread[Thread-1,5,main] did not find value
Thread[Thread-0,5,main] prepare to insert value with id 1
Thread[Thread-0,5,main] commit success and return id = 1
Thread[Thread-1,5,main] prepare to insert value with id 2
Thread[Thread-1,5,main] commit success and return id = 2
Я могу объяснить первые пять строк. Но не может шестая. Когда поток-1 выполняет обновление, полагается, что SELECT id from test WHERE value = ?
имеют пустой результат. Этот результат не согласуется с текущим состоянием БД. Итак, я ожидаю ORA-08177: Cannot serialize access for this transaction
.
Я использую этот impementations класса синхронизации (это сохранить ссылку ссылку на объект потока):
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
public class Sync {
private final Object lock = new Object();
private final Queue<Thread> sleepingTh = new ArrayDeque<>();
private final Set<Thread> activeTh = new HashSet<>();
private final int threads;
public Sync(int threads) {
this.threads = threads;
}
public void yield() {
final Thread ct = Thread.currentThread();
synchronized (lock) {
sleepingTh.add(ct);
activeTh.remove(ct);
if (sleepingTh.size() > threads) {
Thread t = sleepingTh.poll();
activeTh.add(t);
lock.notifyAll();
}
while (!activeTh.contains(ct)) {
try {
lock.wait();
} catch (InterruptedException e) {
}
}
}
}
public void wakeUpAll() {
synchronized (lock) {
activeTh.addAll(sleepingTh);
sleepingTh.clear();
lock.notifyAll();
}
}
}
Заявление для создания таблицы:
create table test(
id number(16),
value varchar(50)
);
Я использую jdk1.8.0_60, Oracle JDBC 10.2.0.4.0 и Oracle DB 11g2
В чем вопрос именно? –
Почему я не ожидаю? ORA-08177: Невозможно сериализовать доступ для этой транзакции? – osseum