com.yegor256.Together Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of together Show documentation
Show all versions of together Show documentation
Executes Java lambda in multiple threads
The newest version!
/*
* The MIT License (MIT)
*
* Copyright (c) 2024 Yegor Bugayenko
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.yegor256;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* Runs lambda function in multiple threads.
*
* Use it like this, in your JUnit5 test (with Hamcrest):
*
* import org.hamcrest.MatcherAssert;
* import org.hamcrest.Matchers;
* import org.junit.jupiter.api.Test;
* import org.junit.jupiter.api.io.TempDir;
* import com.yegor256.Together;
*
* class FooTest {
* @Test
* void worksAsExpected() {
* MatcherAssert.assertThat(
* "processes all lambdas successfully",
* new Together<>(
* () -> {
* // do the job
* return true;
* }
* ),
* Matchers.not(Matchers.hasItem(Matchers.is(false)))
* );
* }
* }
*
* Here, the {@link Together} class will run the "job" in multiple threads
* and will make sure that all of them return {@code true}. If at least
* one of them returns {@code false}, the test will fail. If at least one of the
* threads will throw an exception, the test will also fail.
*
* {@link Together} guarantees that all threads will start exactly
* simultaneously,
* thus simulating race condition as much as it's possible. This is exactly
* what you need for your tests: making sure your object under test
* experiences troubles that are very similar to what it might experience
* in real life.
*
* @param The type of result
* @see How I Test My Java Classes for Thread-Safety
* @see JUnit5
* @see Hamcrest
* @see Race condition
* @see GitHub repository
* @since 0.0.1
*/
public final class Together implements Iterable {
/**
* Total number of threads to use.
*/
private final int threads;
/**
* The action to perform.
*/
private final Together.Action action;
/**
* Ctor.
* @param act The action
*/
public Together(final Together.Action act) {
this(Runtime.getRuntime().availableProcessors(), act);
}
/**
* Ctor.
* @param total The number of threads
* @param act The action
*/
public Together(final int total, final Together.Action act) {
this.threads = total;
this.action = act;
}
@Override
@SuppressWarnings({"PMD.CloseResource", "PMD.DoNotThrowExceptionInFinally"})
public Iterator iterator() {
final CountDownLatch latch = new CountDownLatch(1);
final ExecutorService service =
Executors.newFixedThreadPool(this.threads);
try {
final Collection> futures =
new ArrayList<>(this.threads);
for (int pos = 0; pos < this.threads; ++pos) {
final int thread = pos;
futures.add(
service.submit(
() -> {
latch.await();
return this.action.apply(thread);
}
)
);
}
latch.countDown();
final Collection rets = new LinkedList<>();
for (final Future future : futures) {
try {
rets.add(future.get());
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalArgumentException(ex);
} catch (final ExecutionException ex) {
throw new IllegalArgumentException(ex);
}
}
return new Together.Iter<>(rets, rets.iterator());
} finally {
service.shutdown();
try {
if (!service.awaitTermination(1L, TimeUnit.MINUTES)) {
service.shutdownNow();
if (!service.awaitTermination(1L, TimeUnit.MINUTES)) {
throw new IllegalStateException("Can't shutdown");
}
}
} catch (final InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalArgumentException(ex);
}
}
}
/**
* Turn it into a list.
* @return The list
* @since 0.0.3
*/
public List asList() {
final List list = new LinkedList<>();
for (final T item : this) {
list.add(item);
}
return list;
}
/**
* Action to perform.
*
* @param The type of result
* @since 0.0.1
*/
public interface Action {
/**
* Apply it.
* @param thread The thread number
* @return The result
* @throws Exception If fails
*/
T apply(int thread) throws Exception;
}
/**
* The iterator.
*
* @param The type of result
* @since 0.0.2
*/
private static final class Iter implements Iterator {
/**
* The list of items.
*/
private final Collection items;
/**
* The iterator.
*/
private final Iterator iterator;
/**
* Ctor.
* @param list The list
* @param iter The iterator
*/
Iter(final Collection list, final Iterator iter) {
this.items = list;
this.iterator = iter;
}
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public T next() {
return this.iterator.next();
}
@Override
public String toString() {
final StringBuilder text = new StringBuilder(0).append('[');
for (final T item : this.items) {
if (text.length() > 1) {
text.append(", ");
}
text.append(item);
}
return text.append(']').toString();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy