#java #multithreading #locking #wait #notify
#java #многопоточность #блокировка #подождите #уведомлять
Вопрос:
Я пытаюсь написать небольшой фрагмент кода для блокировки и разблокировки блока кода. функции acquire_lock и release_lock такие, как показано ниже:
public static void acquire_lock(long timestamp) {
synchronized(operations) {
// put the timestamp into queue
operations.add(timestamp);
// check if the head of queue is current timestamp, if not,
// this means there are some other operations ahead of current one
// so current operation has to wait
while (operations.peek() != timestamp) {
try {
operations.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void release_lock() {
synchronized(operations) {
// poll the finished operation out of queue
// and wake up all waiting operations
operations.poll();
operations.notifyAll();
}
}
Но когда я помещаю этот код в тестовую среду, он не всегда работает хорошо,
весь тестовый код выглядит следующим образом:
public class AcquireLockNotWork {
static int balance = 0;
static PriorityQueue<Long> operations = new PriorityQueue<Long>();
// withdraw money from balance
public static void withdraw(final int amt) {
// get system time
Long timestamp = System.nanoTime();
Thread t = new Thread(new Runnable() {
public void run() {
// try to use acquire_lock to lock this piece of code
acquire_lock(timestamp);
try {
Thread.sleep(500);
int holdings = balance;
balance = holdings - amt;
System.out.println("Withdrew " amt " from funds. Now at " balance);
} catch (Exception e) {
e.printStackTrace();
} finally {
release_lock();
}
}
});
t.start();
}
//put money into banlance
public static void deposit(int amt) {
Thread t1 = new Thread(new Runnable() {
public void run() {
Long timestamp = System.nanoTime();
acquire_lock(timestamp);
int holdings = balance;
balance = holdings amt;
System.out.println("deposit " amt ", balance: " balance);
release_lock();
}
});
t1.start();
}
public static void acquire_lock(long timestamp) {
synchronized(operations) {
// put the timestamp into queue
operations.add(timestamp);
// check if the head of queue is current timestamp, if not,
// this means there are some other operations ahead of current one
// so current operation has to wait
while (operations.peek() != timestamp) {
try {
operations.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void release_lock() {
synchronized(operations) {
// poll the finished operation out of queue
// and wake up all waiting operations
operations.poll();
operations.notifyAll();
}
}
public static void test1() {
balance = 0;
deposit(2000);
withdraw(500);
withdraw(1000);
}
public static void main(String[] args) {
test1();
}
}
в течение небольшого количества раз результат будет таким:
deposit 2000, balance: 2000
Withdrew 500 from funds. Now at 500
Withdrew 1000 from funds. Now at 500
это означает, что функции acquire_lock и release_lock не работают должным образом. Кажется, что последние два потока (отозвали 500 и отозвали 1000) вошли в блок между acquire_lock() и release_lock() одновременно, и это не то, что я хочу.
Итак, что не так с функциями acquire_lock и release_lock?
Комментарии:
1. Ваш код, похоже, полагается на
System.nanoTime()
то, что вы никогда не возвращаете одно и то же значение более одного раза, но такое поведение не гарантируется. Javadoc forSystem.nanoTime()
говорит: «Этот метод обеспечивает наносекундную точность, но не обязательно наносекундное разрешение (то есть частоту изменения значения)»
Ответ №1:
Здесь это очень сложно. Аномальное происходит потому, что последний поток сначала вводит acquire_lock. И когда предыдущий поток переходит в acquire_lock, он не будет заблокирован, потому что код блокирует потоки на основе их метки времени. Таким образом, два потока переходят в одну и ту же защищенную область кода.