Взаимное исключение с использованием решения JAVA 2-Threads

Я пытаюсь реализовать решение с двумя потоками, используя алгоритм LockOne (взаимоисключающий). Реализуя этот алгоритм, я пытаюсь разработать поток, определяющий мои собственные методы блокировки, но я не получаю желаемого результата. Когда я запускаю программу... все, что я получаю в качестве вывода, это "Thread-0 Locked" и "Thread-1 Locked".. Может кто-нибудь, пожалуйста, дайте мне знать, где я ошибаюсь? мой код приведен ниже

public class Main {

    static MyLock lock=null;
    static int count=0;

    public static void main(String[] args) throws InterruptedException {

        Thread[] threads=new Thread[2];

        threads[0]=new Thread(new MyThread());
        threads[1]=new Thread(new MyThread());

        lock=new MyLock();

        threads[0].start();
        threads[1].start();
        threads[0].join();
        threads[1].join();

        System.out.println(count);
    }

}

public class MyLock{

    private boolean locked=false;
    private String current; 

    public void lock() {

        if(!locked){
            locked=true;
            current=Thread.currentThread().getName();
        }

        System.out.println(Thread.currentThread().getName()+" locked");
        while(locked && current!=Thread.currentThread().getName());

    }


    public void unlock() {

        System.out.println(Thread.currentThread().getName()+" unlocked");
        locked=false;

    }
}

public class MyThread implements Runnable{

    @Override
    public void run() {

        int i=1;
        while(i<=100){
            Main.lock.lock();
            Main.count++;
            Main.lock.unlock();
            i++;
        }
    }

}

person Trijit    schedule 22.01.2018    source источник
comment
Какой результат вы ожидаете?   -  person Igor Melnichenko    schedule 23.01.2018
comment
На выходе должно быть 200, поскольку счетчик увеличивается в 100 раз каждым из потоков.   -  person Trijit    schedule 23.01.2018
comment
На моей машине программа печатает 200 и завершает работу. Возможно, это зависит от количества доступных ядер процессора. В любом случае, ваша блокировка теперь не является потокобезопасной: - между вызовами lock() и unlock() из разных потоков не возникает никаких отношений. Нет никакой гарантии, что изменения состояния объекта MyLock одним потоком будут видны другим потокам. - процесс блокировки не является атомарным.   -  person Igor Melnichenko    schedule 23.01.2018
comment
Я запускаю эту программу на четырехъядерном ноутбуке i5. Во-вторых... объект MyLock здесь статичен и общедоступен.... поэтому есть только один экземпляр   -  person Trijit    schedule 23.01.2018
comment
Статические и общедоступные модификаторы не делают изменяемый объект потокобезопасным. Проверьте это: docs.oracle.com/javase/tutorial/essential/concurrency /   -  person Igor Melnichenko    schedule 23.01.2018


Ответы (2)


В вашем коде две проблемы.

  1. if(!locked) и установка locked=true не являются атомарными операциями, что означает, что два потока могут найти его незаблокированным и заблокировать его одновременно.
  2. Для переменных locked и current нет синхронизации, поэтому один поток может не прочитать свежее значение, установленное другим потоком, из-за барьер памяти.

Вы можете решить это с помощью AtomicBoolean и volatile:

import java.util.concurrent.atomic.AtomicBoolean;

public class MyLock{

    private AtomicBoolean locked = new AtomicBoolean(false);
    private volatile String current;

    public void lock() {
        for (;;) {
            if(!locked.get()){
                if (locked.compareAndSet(false, true)) {
                    current = Thread.currentThread().getName();
                    System.out.println(current + " locked");
                    return;
                }
            }
        }
    }


    public void unlock() {
        System.out.println(current + " unlocked");
        locked.set(false);
    }
}
person xingbin    schedule 23.01.2018

И еще одно решение на случай, если вам нужна реентерабельная блокировка с политикой честного приобретения:

public class MyLock
{
    private String holder;
    private Queue<Long> next = new ConcurrentLinkedQueue<>();

    private Object syncLock = new Object();

    public void lock()
    {
        Long threadId = Thread.currentThread().getId();
        this.next.add(threadId);
        boolean acquired = false;

        while (!acquired)
        {
            synchronized (this.syncLock)
            {
                if (this.holder == null)
                {
                    if (this.next.peek() == threadId)
                    {
                        this.holder = Thread.currentThread().getName();
                        System.out.println(this.holder + " locked");

                        acquired = true;
                    }
                }
                else
                {
                    if (this.holder.equals(Thread.currentThread().getName()))
                    {
                        acquired = true;
                    }
                }
            }
        }

        this.next.remove(threadId);
    }

    public void unlock()
    {
        synchronized (this.syncLock)
        {
            System.out.println(Thread.currentThread().getName() + " unlocked");
            this.holder = null;
        }
    }
}
person Igor Melnichenko    schedule 23.01.2018
comment
На самом деле я знаю, как использовать реентерабельные блокировки... я просто хотел реализовать свою версию взаимного исключения для двухпотокового решения... но спасибо... - person Trijit; 23.01.2018