#java #multithreading
#java #многопоточность
Вопрос:
Я написал следующий тестовый код на Java с использованием ReentrantReadWriteLock, чтобы понять разницу между справедливым и несправедливым режимом. Однако я вижу, что в обоих режимах результат и вывод всегда одинаковы. Кажется, он всегда работает в честном режиме. Кто-нибудь может объяснить, в каком случае справедливый и несправедливый режимы приведут к различному поведению?
package lockTest;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MyLockTest {
static private ReadWriteLock myLock = new ReentrantReadWriteLock(false);
public class Reader extends Thread {
int val_;
public Reader(int val) {
val_ = val;
}
public void run() {
if (val_ > 0) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
myLock.readLock().lock();
System.out.println(Thread.currentThread().getName() ": Reader inside critical section - val: " val_ "-----");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myLock.readLock().unlock();
}
}
public class Writer extends Thread {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() ": Writer inside critical section *****");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myLock.writeLock().unlock();
}
}
public static void main(String[] args) {
MyLockTest test1 = new MyLockTest();
MyLockTest.Reader reader1 = test1.new Reader(0);
MyLockTest.Writer writer1 = test1.new Writer();
MyLockTest.Reader reader2 = test1.new Reader(1);
reader2.start();
writer1.start();
reader1.start();
}
}
Вывод всегда:
Thread-0: Reader inside critical section - val: 0-----
Thread-1: Writer inside critical section *****
Thread-2: Reader inside critical section - val: 1-----
Приведенный выше результат — это то, что я ожидаю увидеть, когда я переключу создание блокировки в режим fair:
static private ReadWriteLock myLock = new ReentrantReadWriteLock(true);
Для несправедливого режима я ожидал бы увидеть следующий вывод:
Thread-0: Reader inside critical section - val: 0-----
Thread-2: Reader inside critical section - val: 1-----
Thread-1: Writer inside critical section *****
Комментарии:
1. Я не уверен в каких-либо деталях, но я думаю, что это будет сильно зависеть от базовой платформы. Поэтому, если вам требуется «справедливое» поведение, укажите его; не полагайтесь на результаты тестирования в одной конкретной среде.
2. Неясно, что вы подразумеваете под «действительно работой» здесь. Вы спрашиваете о поведенческих различиях или деталях реализации?
3. «несправедливого» режима не существует. Два режима — «честный» и «по умолчанию». API библиотеки требует, чтобы режим fair вел себя определенным образом, но API не требует, чтобы режим по умолчанию вел себя каким-либо особым образом. В частности, он не требует, чтобы режим по умолчанию отличался от режима fair.
4. @SolomonSlow: Я не уверен, что вы подразумеваете под «нечестным» режимом». Документация для
ReentrantReadWriteLock
( docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks /… ) явно имеет заголовок в разделе «Заказ на приобретение» с надписью «Несправедливый режим (по умолчанию)», который начинается с «При построении как несправедливый …» Несправедливость — это действительно распространенный термин во взаимном исключении, который означает неопределенный порядок получения.5. Наверное, я плохо это сказал. Да, в документации используется фраза «несправедливо», но в документации нет ничего, что требовало бы, чтобы «несправедливый» режим не был справедливым. Реализация, которая просто игнорирует аргумент
fair
конструктора и всегда создает справедливую блокировку, была бы совершенно законной.
Ответ №1:
Использование режима «справедливый» или «несправедливый» влияет на то, как блокировка назначается потокам в случае конфликта.
Из Javadoc для ReentrantReadWriteLock: при использовании «несправедливого» режима порядок входа в блокировку чтения и записи не указан, при использовании «справедливого» режима потоки конкурируют за вход, используя приблизительно политику порядка поступления.
Мы можем видеть, как использование fair / non-fair влияет на выполнение программы, поскольку некоторые потоки конкурируют за одну и ту же блокировку; смотрите Программу ниже.
Запустив пример кода, a ReentrantWriteLock
оспаривается разными потоками; после 1000 операций блокировки мы сбрасываем, сколько раз каждый поток получал блокировку.
В случае USE_FAIR=false
, если используется, подсчеты являются случайными, и возможный результат:
Thread thread-B finished, count=920
Thread thread-A finished, count=79
Thread thread-D finished, count=0
Thread thread-C finished, count=0
в случае USE_FAIR=true
, если используется, вывод всегда имеет вид
Thread thread-D finished, count=249
Thread thread-A finished, count=250
Thread thread-C finished, count=250
Thread thread-B finished, count=250
Пример кода
package sample1;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class UseLock {
public static void main(String[] args) {
UseLock o = new UseLock();
o.go();
}
private void go() {
TotalPermits tp = new TotalPermits();
tp.lock.writeLock().lock();
Contender a = new Contender(tp, "thread-A");
Contender b = new Contender(tp, "thread-B");
Contender c = new Contender(tp, "thread-C");
Contender d = new Contender(tp, "thread-D");
a.start();
b.start();
c.start();
d.start();
tp.lock.writeLock().unlock();
}
}
class TotalPermits {
private static final boolean USE_FAIR = true;
private int count = 1_000;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(USE_FAIR);
public boolean get() {
try {
lock.writeLock().lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) { }
return --count>0;
} finally {
lock.writeLock().unlock();
}
}
}
class Contender extends Thread {
private int count = 0;
final String name;
final TotalPermits tp;
Contender(TotalPermits tp, String name) {
this.tp = tp;
this.name = name;
}
@Override
public void run() {
while ( tp.get() ) {
count ;
}
System.out.printf("Thread %s finished, count=%d%n", name, count);
}
}
Примечание:
В приведенном выше примере кода используется блокировка «запись», которая может удерживаться только одним потоком одновременно. Таким образом, мы можем использовать это для разделения N разрешений между претендентами. С другой стороны, блокировка «чтения» может удерживаться несколькими потоками, если ни один из них не удерживает блокировку записи.
Комментарии:
1. Спасибо. Ваш ответ правильный. Что-то, что нужно добавить, это то, что я ориентировал вопрос на справедливость между блокировками записи и чтения, опасаясь, что автор будет голодать сотнями читателей. Однако концепция справедливости шире и относится к любому типу конфликтов писатель-писатель, писатель-читатель, читатель-читатель.