Программирование мьютекса на Java

Я новичок в информатике, и я читаю книгу, в которой представлены потоки и мьютексы. Я пробовал программировать мьютекс на Java, который, кажется, работает большую часть времени, но время от времени это не так.

В моем коде критическая секция добавляет числа от 1 до 10 к статической переменной j, что дает 55 (если j начинается с 0). Если я запускаю три потока одновременно через критическую секцию, я получаю случайные конечные значения j, что имеет смысл.

Но с приведенным ниже мьютексом большую часть времени я получаю конечное значение j 165 (55 * 3), что мне и нужно, но иногда я получаю случайные значения j. Может ли кто-нибудь взглянуть на мой код и посмотреть, что происходит? Спасибо!

public class Mythread extends Thread {

    private static int j = 0;
    private static int mutex = 0;  // Initial value of the mutex is 0;

    @Override
    public void run() {


        while (test_and_set(mutex) == 1) {

            // wait here if mutex is 1
            System.out.println("Thread waiting..");
        } 

        for (int i = 1; i <= 10; i++) { // Start of Critical section 
            j += i;                     // 

        }
        System.out.println(j);       // End of Critical section 
                                     // Should add on 55 to j if one thread is running through the CS

       mutex = 0; // Thread that has finished the CS sets the mutex to 0.

    }

    public static int test_and_set(int oldMutexValue) {
        if (mutex == 0) {
            mutex = 1;
        }
        return oldMutexValue;
    }


}

public class Test1 {




    public static void main(String[] args) {

        Mythread thread1 = new Mythread();
        Mythread thread2 = new Mythread();
        Mythread thread3 = new Mythread();
        thread1.start();
        thread2.start();
        thread3.start();

    }



}

person mmgro27    schedule 09.10.2014    source источник
comment
Ваши критические разделы не защищены никакой синхронизацией.   -  person Sotirios Delimanolis    schedule 09.10.2014
comment
@MarkW volatile не поможет в test_and_set. Действие не атомарное.   -  person Sotirios Delimanolis    schedule 09.10.2014
comment
Хороший звонок @SotiriosDelimanolis   -  person Mark W    schedule 09.10.2014
comment
@SotiriosDelimanolis Не могли бы вы объяснить, что вы подразумеваете под синхронизацией? Я очень новичок в этом. Редактировать: На самом деле, это будет рассмотрено позже в моей книге, так что я буду читать дальше!   -  person mmgro27    schedule 09.10.2014
comment
Также читайте условия гонки.   -  person Sotirios Delimanolis    schedule 09.10.2014
comment
гугл джава синхронизирован   -  person Kalpesh Soni    schedule 09.10.2014


Ответы (3)


Вы создаете состояние гонки и используете вращающуюся блокировку. Вращающиеся блокировки не рекомендуются в Java. Рассмотрим следующее:

Один поток начинает выполняться, а два других ждут. Два других потока ждут. Два других потока теперь устанавливают мьютексы одновременно и оба начинают выполняться, что дает вам нечетное значение J, потому что они оба изменяют его одновременно.

Что исправить:

Внедрите методологию synchronized Java. Synchronized — это внутренний метод Java для обеспечения безопасности потоков и контроля. Никаких вращающихся замков!

Измените MyThread на следующее:

public class MyThread extends Thread {
private static int j = 0;

public void run() {
    synchronized(this) {
        for (int i = 1; i <= 10; i++) {
            j += i;
        }
    }
    System.out.println(j);
}

Synchronized может окружать любые критические части кода, которые могут привести к условиям гонки, одновременному изменению данных и т. д. Обратите внимание на отсутствие специальной блокировки. Обратите внимание, что synchronized принимает свой собственный объект в качестве параметра, так как вы синхронизируете контроль над этим объектом, но при желании вы можете так же легко использовать другие объекты в качестве параметра, что дает вам больше гибкости при блокировке.

Остальная часть вашего кода будет работать точно так же!

Надеюсь это поможет!

person Andrew M    schedule 09.10.2014
comment
Хороший ответ, но не на вопрос, который задал ОП. ОП не хочет использовать synchronized: он/она пытается узнать, как работает synchronized, написав свою собственную версию. - person Solomon Slow; 10.10.2014
comment
synchronized(this) на самом деле здесь не помогает. Несколько потоков должны синхронизироваться на одном общем объекте. - person Radiodef; 10.10.2014

(1) Ваша функция test_and_set() выглядит так, как будто она должна эмулировать аппаратную инструкцию с таким же именем, которая использовалась во многих компьютерных архитектурах. Но что должно возвращать test_and_set()? Подсказка: он должен сообщать вызывающей стороне, «выиграл» ли вызывающий мьютекс.

(2) С вашим test_and_set() есть большая проблема: он не атомарный. Если вы задавались вопросом, почему компьютер с инструкциями «тест» и «установка» также должен иметь инструкцию test_and_set (также известную как TAS), это потому, что инструкция TAS является атомарной. Я предполагаю, что вам нужно больше узнать о том, что это значит (см. 4 ниже).

(3) ТАС архаичен. Современные компьютеры реализуют нечто более мощное, называемое «Сравнить и поменять местами» или «Сравнить и установить» (CAS). Если вы хотите узнать, как работают современные алгоритмы синхронизации, вам следует использовать CAS вместо TAS.

В стандартной библиотеке Java есть реальная, действительно атомарная функция CAS, которую вы можете вызвать: это java.util.concurrent.atomic.AtomicInteger.CompareAndSet(...). http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger.html

(4) Вероятно, вам не следует пытаться научиться писать функции/объекты синхронизации, пока вы не приобретете больше опыта использования их. Что вас выдает, так это то, что вы не синхронизировали свою функцию test_and_set(). Если вы этого не знали, значит, у вас нет большого опыта написания многопоточного кода.

Сначала узнайте, как использовать объекты синхронизации. http://docs.oracle.com/javase/tutorial/essential/concurrency/ Затем вы можете беспокоиться о том, как они реализованы.

person Solomon Slow    schedule 09.10.2014
comment
Люблю информацию о скрытых деталях реализации! Особенно рад видеть этот ответ, так как OP явно просит об этом, ха-ха. - person Sam Malayek; 15.05.2018

Для вашей задачи есть класс java.util.concurrency.Semaphore:

package test;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class MyThread extends Thread {
    private static int j = 0;
    private static final Semaphore mutex = new Semaphore(1, true);

    @Override
    public void run() {
        try {
            while (!mutex.tryAcquire(100, TimeUnit.MILLISECONDS)) {
                System.out.println("Thread waiting.");
            }
        }
        catch (InterruptedException e) {
            System.out.println("Thread interrupted.");
            return;
        }

        try {
            for (int i = 1; i <= 10; i++) { // Start of Critical section
                j += i;                     //

            }
            System.out.println(j);       // End of Critical section
            // Should add on 55 to j if one thread is running through the CS
        }
        finally {
            mutex.release(); // Thread that has finished the CS sets the mutex to 0.
        }
    }
}
person ursa    schedule 09.10.2014