Страницы

Поиск по вопросам

суббота, 8 февраля 2020 г.

Как правильно сделать синхронизацию?

#java #синхронизация


Практикуюсь работать с синхронизацией потоков и не получается сделать так, как придумал.
У меня есть аккаунт в банке, муж и жена кладут туда деньги. Я хочу сделать так, чтоб
они по очереди это делали. Я делаю это так:

public class Main {
    public static void main(String[] args) {
        new Wife().start();
        new Husband().start();
    }
}

public class Husband extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println("Husband " + i + " " +   Account.deposit());
        }
    }
}

public class Wife extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 10; i++) {
            System.out.println("Wife " + i + " " + Account.deposit());
        }
    }
}

public class Account {
    public synchronized static int deposit(){
        account += 300;
        return account;
    }
}


Но все равно получается бардак. Может вместо synchronized нужно семафоры использовать?
Чтоб была правильная очередность.
    


Ответы

Ответ 1



Сейчас нет возможности обдумать элегантное решение, но я бы приоритеты задавал. Вот решение в лоб: public class App { public static void main(String[] args) { Account account = new Account(0); new Wife(account).start(); new Husband(account).start(); } public static abstract class FamilyMember extends Thread { protected Account mAccount; protected String mId; public FamilyMember(Account account) { mAccount = account; } @Override public void run() { super.run(); startMe(); } protected abstract void startMe(); } public static class Husband extends FamilyMember { public Husband(Account account) { super(account); mId = getClass().getName(); mAccount.registerMember(getClass().getName()); } @Override protected void startMe(){ for (int i = 0; i < 10; i++) { try { while(!mAccount.CheckPriority(mId)) Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Husband " + i + " " + mAccount.deposit(mId)); } } } public static class Wife extends FamilyMember { public Wife(Account account) { super(account); mId = getClass().getName(); mAccount.registerMember(getClass().getName()); } @Override protected void startMe(){ for (int i = 0; i < 10; i++) { try { while(!mAccount.CheckPriority(mId)) Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Wife " + i + " " + mAccount.deposit(mId)); } } } public static class Account { private int mAccount = 0; private List mMembers = new ArrayList(); /** * проверяем имеет ли член семьи максимальный приоритет * @param id * @return */ public synchronized boolean CheckPriority(String id){ Member member = mMembers.stream().max(Comparator.comparingInt(Member::getPriority)).get(); return member.getId().equals(id); } public void registerMember(String id){ mMembers.add(new Member(id, 0)); } public Account(int account) { mAccount = account; } public synchronized int deposit(String id) { mAccount += 300; // понижаем приоритет mMembers.stream().filter(member -> member.mId.equals(id)).findFirst().get().decPriority(); return mAccount; } public static class Member{ public String mId; public int mPriority; public Member(String id, int priority){ mId = id; mPriority = priority; } public void decPriority(){ mPriority--; } public int getPriority(){ return mPriority; } public String getId(){ return mId; } } } } Выведет: Wife 0 300 Husband 0 600 Wife 1 900 Husband 1 1200 Wife 2 1500 Husband 2 1800 Wife 3 2100 Husband 3 2400 Wife 4 2700 Husband 4 3000 Wife 5 3300 Husband 5 3600 Wife 6 3900 Husband 6 4200 Wife 7 4500 Husband 7 4800 Wife 8 5100 Husband 8 5400 Wife 9 5700 Husband 9 6000 Потоки, перед тем как внести деньги на счёт, проверяют свой приоритет с помощью CheckPriority. Если текущий поток имеет больший приоритет, то вносятся деньги mAccount.deposit(mId) и понижается текущий приоритет mMembers.stream().filter(member -> member.mId.equals(id)).findFirst().get().decPriority(); Иначе засыпает на 1мс.

Ответ 2



Пример синхронизации с использованием wait() / notify(): public class Main { public static void main(String[] args) { Account account = new Account(); new Thread(new Wife(account)).start(); new Thread(new Husband(account)).start(); } } public class Account { private int account; public boolean turn; public synchronized int deposit() { turn = !turn; account += 300; return account; } } public abstract class FamilyMember implements Runnable { private final String name; protected final Account account; public FamilyMember(String name, Account account) { this.name = name; this.account = account; } public abstract boolean isMyTurn(); public void run() { for (int i = 0; i < 10; i++) { synchronized (account) { while (!isMyTurn()) { try { account.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name + " " + i + " " + account.deposit()); account.notify(); } } } } public class Wife extends FamilyMember { public Wife(Account account) { super("Wife", account); } @Override public boolean isMyTurn() { return !account.turn; } } public class Husband extends FamilyMember { public Husband(Account account) { super("Husband", account); } @Override public boolean isMyTurn() { return account.turn; } } В счете храниться флажок turn, который определяет, чья очередь класть деньги. Общий код переместился в класс FamilyMember, муж и жена ждут своей очереди с помощью метода isMyTurn(). Конечно, коряво получилось, но представление об использовании wait() и notify() дает. Это решение похоже на решение Suvitruf'а, только вместо Thread.sleep() для ожидания, пока условие не выполниться, используется wait(), что эффективнее, так как программа крепче спит. Метод notify() уведомляет об изменении условия и пробуждает ожидающий поток. wait() нужно использовать в цикле, потому что поток может проснуться и без вызова notify(). Вместо флага account.turn, наверное, можно использовать значение счета. Если сумма на счете изменилась, то очередь класть деньги переходит к другому. Для этого нужно будет добавить синхронизированный метод Account.get(), который будет возвращать текущую сумму на счете. Муж/жена запоминают сумму на счете, после того как доложили деньги, и ждут, пока сумма изменится.

Ответ 3



Можно и семафоры. Если с synchronized то нужно использовать конструкцию wait()/notify(): муж делает вклад - жена "спит", жена делает вклад - "засыпает" муж.

Комментариев нет:

Отправить комментарий