Как правильно синхронизировать два потока - PullRequest
0 голосов
/ 11 января 2019

Это проблема, о которой я всегда слышал в школе, но у меня никогда не было причин возиться, пока меня не попросили пройти собеседование.

Подсказка: Используя 2 потока, выведите "Thread i: The number is 'j'" в порядке, где j = 1: 100, а i - номер потока. Нить 1 может печатать только нечетные j, а нить 2 может печатать только четные j.

РЕДАКТИРОВАТЬ выход j должен быть заказан

Это была моя попытка, но я не пошел дальше в процессе собеседования. Есть ли какая-то фундаментальная часть, по которой я скучаю? Есть ли оптимизации?

import java.util.concurrent.Semaphore;

public class ThreadSynchronization implements Runnable {

  private int start;
  private Semaphore semaphore;

  private ThreadSynchronization(int start, Semaphore semaphore) {
      this.start = start;
      this.semaphore = semaphore;
  }

  public static void main(String[] args) {
      Semaphore semaphore = new Semaphore(1, true);
      semaphore.acquireUninterruptibly();

      start(1, semaphore);
      start(2, semaphore);

      semaphore.release();
  }

  private static void start(int start, Semaphore semaphore) {
      ThreadSynchronization ts = new ThreadSynchronization(start, semaphore);
      Thread thread = new Thread(ts);
      thread.start();
      while (thread.getState() != Thread.State.WAITING) ;
  }

  @Override
  public void run() {
      for (int i = start; i <= 100; i += 2) {
          semaphore.acquireUninterruptibly();
          System.out.println("Thread " + start + ": The number is '" + i + "'");
          semaphore.release();
      }
  }
}

Ответы [ 5 ]

0 голосов
/ 11 января 2019

Вы были очень близки к правильному решению, но для задачи требуется 2 семафора:

public class ThreadSynchronization implements Runnable {

    private int start;
    private Semaphore semaphore1;
    private Semaphore semaphore2;

    private ThreadSynchronization(int start, Semaphore semaphore1, Semaphore semaphore2) {
        this.start = start;
        this.semaphore1 = semaphore1;
        this.semaphore2 = semaphore2;
    }

    private static void start(int start, Semaphore semaphore1, Semaphore semaphore2) {
        ThreadSynchronization ts = new ThreadSynchronization(start, semaphore1, semaphore2);
        Thread thread = new Thread(ts);
        thread.start();
    }

    @Override
    public void run() {
        for (int i = start; i <= 100; i += 2) {
            semaphore1.acquireUninterruptibly();
            System.out.println("Thread " + start + ": The number is '" + i + "'");
            semaphore2.release();
        }
    }

    public static void main(String[] args) {
        Semaphore semaphore1 = new Semaphore(1);
        Semaphore semaphore2 = new Semaphore(0);

        start(1, semaphore1, semaphore2);
        start(2, semaphore2, semaphore1); // in reverse order
    }
}
0 голосов
/ 11 января 2019

Использование объекта для арбитража:

public class Switch {
    private boolean expected;

    public Switch(boolean init) {
        expected = init;
    }

    public void waitFor(boolean value) {
        synchronized(this) {
            while (value != expected) {
                try {
                    wait();
                } catch (InterruptedException ex) {
                    // deal with it
                }
            }
            expected = !expected;
            notifyAll();
        }
    }
}

Тогда:

public class ThreadSynchronization implements Runnable {
    private static Switch arbiter = new Switch(true);

    private int start;

    private ThreadSynchronization(int start) {
        this.start = start;
    }

    public static void main(String[] args) {
        start(1);
        start(2);
    }

    private static void start(int start) {
        ThreadSynchronization ts = new ThreadSynchronization(start);
        Thread thread = new Thread(ts);
        thread.start();
    }

    @Override
    public void run() {
        boolean odd = start%2 != 0;
        for (int i = start; i <= 100; i += 2) {
            arbiter.waitFor(odd);
            System.out.println("Thread " + start + ": The number is '" + i + "'");
        }
    }
}
0 голосов
/ 11 января 2019

Для этой простой задачи достаточно использовать AutomicInteger:

 public static class CounterTask implements Runnable {

    private final int id;
    private final AtomicInteger counter;
    private final int max;
    private final IntPredicate predicate;

    public CounterTask(int id, AtomicInteger counter, int max, IntPredicate predicate) {
        this.id = id;
        this.counter = counter;
        this.max = max;
        this.predicate = predicate;
    }

    @Override
    public void run() {
        while (counter.get() <= max) {
            if (predicate.test(counter.get())) {
                System.out.format("Thread %d: The number is '%d'\n", id, counter.get());
                counter.incrementAndGet();
            }
        }
    }
}

public static void main(String... args) throws InterruptedException {
    final int max = 100;
    final AtomicInteger counter = new AtomicInteger();
    Thread oddThread = new Thread(new CounterTask(1, counter, max, val -> val % 2 == 0));
    Thread evenThread = new Thread(new CounterTask(2, counter, max, val -> val % 2 != 0));

    oddThread.start();
    evenThread.start();

    oddThread.join();
    evenThread.join();
}
0 голосов
/ 11 января 2019

Хотя wait и notify могут выполнять эту работу, я думаю, использование Semaphore может сделать код более читабельным. Приведенный ниже код сфокусирован на решении для потоков, «говорящих» друг с другом и синхронизирующих при необходимости: я представляю, что в реальном случае 2 потока выполняют важную работу, и в какой-то момент необходимо выполнить синхронизацию и определить, кто идет первым.

import java.util.concurrent.Semaphore;

public class LockStep {

    public static void main(String[] args) {

        Semaphore evenTurn = new Semaphore(1);
        Semaphore oddTurn = new Semaphore(0);

        int max = 50;

        Thread even = new Thread(new Worker(evenTurn, oddTurn, max));
        even.start();
        Thread odd = new Thread(new Worker(oddTurn, evenTurn, max));
        odd.start();

        try {
            even.join();
            odd.join();
        } catch (Exception e) {
            System.out.println("No join for me: " + e);
        }
        System.out.println("Finished");
    }

    static class Worker implements Runnable {

        final Semaphore myTurn;
        final Semaphore theirTurn;
        final int maxTurns;

        public Worker(Semaphore myTurn, Semaphore theirTurn, int maxTurns) {
            this.myTurn = myTurn;
            this.theirTurn = theirTurn;
            this.maxTurns = maxTurns;
        }

        @Override
        public void run() {

            int turn = 0;
            while (turn < maxTurns) {
                try {
                    myTurn.acquire();
                    turn += 1;
                    System.out.println(Thread.currentThread().getName() + " " + turn);
                    theirTurn.release();
                } catch (Exception e) {
                    System.out.println("Oops: " + e);
                }
            }
        }
    }
}
0 голосов
/ 11 января 2019

Одна нить может продолжать получать и отпускать Semaphore, а другая нить голодает.

Вы можете сделать это с wait и notify, попробуйте это:

import java.util.concurrent.atomic.AtomicInteger;

class Odd implements Runnable {

    private AtomicInteger integer;
    private final Object lock;

    public Odd(AtomicInteger integer, Object lock) {
        this.integer = integer;
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            try {
                while (integer.get() <= 100) {
                    while (integer.get() % 2 == 0) {
                        lock.notify();
                        lock.wait();
                    }
                    if (integer.get() <= 100) {
                        System.out.println("Thread " +
                                Thread.currentThread().getName() + ": The number is '" + integer.get() + "'");
                    }
                    integer.getAndIncrement();
                    lock.notify();
                }
            } catch (Exception e) {

            }
        }
    }
}

class Even implements Runnable {

    private AtomicInteger integer;
    private final Object lock;

    public Even(AtomicInteger integer, Object lock) {
        this.integer = integer;
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            try {
                while (integer.get() <= 100) {
                    while (integer.get() % 2 != 0) {
                        lock.notify();
                        lock.wait();
                    }
                    if (integer.get() <= 100) {
                        System.out.println("Thread " +
                                Thread.currentThread().getName() + ": The number is '" + integer.get() + "'");
                    }

                    integer.getAndIncrement();
                    lock.notify();
                }
            } catch (Exception e) {

            }
        }
    }
}

public class ThreadSynchronization {

    public static void main(String[] args) throws Exception{
        Object lock = new Object();
        AtomicInteger integer = new AtomicInteger(1);
        Odd odd = new Odd(integer, lock);
        Even even = new Even(integer, lock);

        Thread thread1 = new Thread(odd, "1");
        Thread thread2 = new Thread(even, "2");

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }

}
...