All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yegor256.Together Maven / Gradle / Ivy

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