What would be a realistic use case where java.util.Exchanger would be the best choice of synchronizer?
I've seen snippets from GitHub and tutorial sites but they always seem contrived and better solved with a TransferQueue
Exchanger
and TransferQueue
are very different, basically TransferQueue
can fill all your memory if the consumer is very slow, while Exchanger
uses constant memory, producing a synchronization when the paired threads are ready.
Obviously the use of one or the other will have drastic consequences on the efficiency of synchronization and workflow (but also on the determinism in the use of resources).
Note: the reasons for the producer and consumer to request synchronization may be different reasons, not only that they have a full buffer, it could be for example that one has been left waiting for the other. Also, with Exchange
producer/consumer are confused (the consumer can deliver results to the producer), while with TransferQueue
you should create additional structures.
As an example, think of your concurrent process as a piping infrastructure and the nodes move water up and down (the nodes contain water pumps).
Also, the pumping effect move across all infrastructure (like a wave), think your concurrent process use one thread result to be used on the other thread and so on.
(Comments in code)
package com.computermind.sandbox.concurrent;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ThreadLocalRandom;
public class ExchangerTransferQueue {
// helper for wait
@SneakyThrows
public static void _wait() {
Thread.sleep(ThreadLocalRandom.current().nextInt(1_000, 5_000));
}
// the pumping capacity is the work a node must to do, this work
// is moving across threads then, the `PumpingCapacity` "P1" will
// be used by the producer but later (when water move) "P1" will be
// used by consumer and so (the `exchange` sync move PumpingCapacity
// instances)
@AllArgsConstructor
static
class PumpingCapacity {
String name;
int from;
int to;
// do "up" work (if any)
boolean up(String node) {
if(from < 1) return false;
System.out.printf("~ node %s pump up %s%n", node, name);
from -= 1; to += 1;
return true;
}
// do "down" work (if any)
boolean down(String node) {
if(to < 1) return false;
System.out.printf("~ node %s pump down %s%n", node, name);
from += 1; to -= 1;
return true;
}
}
// the producer have a initial PumpingCapacity and
// create the exchange to the next node
static class WaterPumpProducer implements Runnable {
PumpingCapacity p;
final Exchanger<PumpingCapacity> b = new Exchanger<>();
WaterPumpProducer(PumpingCapacity p) {
this.p = p;
}
@SneakyThrows
@Override
public void run() {
// for ever
while(true) {
// do work
while (p.up("Producer")) _wait();
// and exchange
System.out.println("Producer need change");
p = b.exchange(p);
}
}
}
// an interemediate node have two PumpingCapacity one working
// with the predecessor and other with the successor
static class WaterPumpNode implements Runnable {
PumpingCapacity p, q;
final Exchanger<PumpingCapacity> a;
final Exchanger<PumpingCapacity> b = new Exchanger<>();
WaterPumpNode(PumpingCapacity p, PumpingCapacity q, Exchanger<PumpingCapacity> a) {
this.p = p;
this.q = q;
this.a = a;
}
@SneakyThrows
@Override
public void run() {
while(true) {
while (p.down("Node")) _wait();
while (q.up("Node")) _wait();
System.out.println("Node need change");
p = a.exchange(p);
q = b.exchange(q);
}
}
}
static class WaterPumpConsumer implements Runnable {
PumpingCapacity p;
final Exchanger<PumpingCapacity> a;
WaterPumpConsumer(PumpingCapacity initialCapacity, Exchanger<PumpingCapacity> a) {
p = initialCapacity;
this.a = a;
}
@SneakyThrows
@Override
public void run() {
while(true) {
while (p.down("Consumer")) _wait();
System.out.println("Consumer need change");
p = a.exchange(p);
}
}
}
@SneakyThrows
public static void main(String... args) {
WaterPumpProducer producer = new WaterPumpProducer(new PumpingCapacity("P1", 5, 0));
WaterPumpNode node = new WaterPumpNode(new PumpingCapacity("P2", 0, 3), new PumpingCapacity("P3", 3, 0), producer.b);
WaterPumpConsumer consumer = new WaterPumpConsumer(new PumpingCapacity("P4", 0, 2), node.b);
// consumer run first, the consumer do job!
new Thread(consumer).start();
// wait to see consumer wait
Thread.sleep(15_000);
new Thread(node).start();
// wait to see node wait
Thread.sleep(15_000);
new Thread(producer).start();
// see how PumpingCapacities up and down across all pipe infrastructure
}
}
with output (with comments)
~ node Consumer pump down P4 <-- only consumer is working
~ node Consumer pump down P4
Consumer need change <-- and stop since need change
~ node Node pump down P2 <-- when node start do job
~ node Node pump down P2
~ node Node pump down P2
~ node Node pump up P3
~ node Node pump up P3
~ node Node pump up P3
Node need change <-- and need change and wait producer
~ node Producer pump up P1 <-- when producer start do job
~ node Producer pump up P1
~ node Producer pump up P1
~ node Producer pump up P1
~ node Producer pump up P1
Producer need change <-- here all nodes work and
~ node Producer pump up P2 PumpingCapacities go up and down
~ node Consumer pump down P3 moving water
~ node Node pump down P1
~ node Node pump down P1
~ node Consumer pump down P3
~ node Producer pump up P2
~ node Node pump down P1
~ node Consumer pump down P3
~ node Producer pump up P2
~ node Node pump down P1
Consumer need change
Producer need change
~ node Node pump down P1
~ node Node pump up P4
~ node Node pump up P4
Node need change
~ node Node pump down P2
~ node Consumer pump down P4
~ node Producer pump up P1
...
the memory usage is constant (would not be if you were to use TransferQueue
).
Then
What would be a realistic use case where java.util.Exchanger would be the best choice of synchronizer?
any in which two processes are mutually synchronized to perform a task in which the work of one influences the other and vice versa. The producer/consumer pattern in which the producer must wait for the consumer (ie and thus not accumulate excessive work) may be a good example.
rather than move it along to another thread for further action?
Because both threads are sharing information and you want the actions to be done concurrently, so you can't just pass it from one thread to the other.
Suppose you have the following recurrence function
G{i+1} = g( H{i}, G{i} )
H{i+1} = h( H{i}, G{i} )
you can start for every step two threads to run in parallel both g
and h
or you could use Exchanger
and start only once two threads.
Of course you can use a lot of other structures but you will see that in those you have to think about the possibility of a deadlock while Exchanger
makes that exchange simple and safe.
For example, suppose we want to perform a certain biological simulation (ie https://en.wikipedia.org/wiki/Nicholson%E2%80%93Bailey_model )
static void NicholsonBailey(double H, double P, double k, double a, double c, AtomicBoolean stop) {
// H and P exchange their values (H get p and P get H)
final Exchanger<Double> e = new Exchanger<>();
// H function
new Thread(() -> { try {
double h = H, p = P;
while(!stop.get()) {
h = k * h * Math.exp(-a * p); // expensive
p = e.exchange(h);
}
e.exchange(0., 1, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException ex) { /* end */ }}).start();
// P function
new Thread(() -> { try {
double h = H, p = P;
while(!stop.get()) {
System.out.printf("(H, P) := (%e, %e)%n", h, p);
p = c * h * (1 - Math.exp(-a * p)); // expensive
h = e.exchange(p);
}
e.exchange(0., 1, TimeUnit.MILLISECONDS);
} catch (InterruptedException | TimeoutException ex) { /* end */ }}).start();
}
@SneakyThrows
public static void main(String... args) {
AtomicBoolean stop = new AtomicBoolean(false);
double k = 2, a = 0.02, c = 1;
NicholsonBailey(Math.log(k) / a + 0.3, (k * Math.log(k)) / ((k - 1) * a * c) + 0.3, k, a, c, stop);
// run simulation until stop
Thread.sleep(100);
stop.set(true);
}
just by using Exchanger
we can easily synchronize both calculations.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.