diff --git a/HW1-Lazy/build.gradle b/HW1-Lazy/build.gradle new file mode 100644 index 0000000..906d587 --- /dev/null +++ b/HW1-Lazy/build.gradle @@ -0,0 +1,89 @@ +group 'JavaHW' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.2' + classpath 'com.google.guava:guava:23.5-jre' + classpath 'org.mockito:mockito-all:2.0.2-beta' + } +} + +repositories { + mavenCentral() +} + +ext.junit4Version = '4.12' +ext.junitVintageVersion = '4.12.2' +ext.junitPlatformVersion = '1.0.2' +ext.junitJupiterVersion = '5.0.2' +ext.log4jVersion = '2.9.0' + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'org.junit.platform.gradle.plugin' + +jar { + baseName = 'junit5-gradle-consumer' + version = '1.0.0-SNAPSHOT' +} + +compileTestJava { + sourceCompatibility = 1.9 + targetCompatibility = 1.9 + options.compilerArgs += '-parameters' +} + +junitPlatform { + // platformVersion '1.0.2' + filters { + engines { + // include 'junit-jupiter', 'junit-vintage' + // exclude 'custom-engine' + } + tags { + // include 'fast' + exclude 'slow' + } + // includeClassNamePattern '.*Test' + } + // configurationParameter 'junit.jupiter.conditions.deactivate', '*' + // enableStandardTestTask true + // reportsDir file('build/test-results/junit-platform') // this is the default + logManager 'org.apache.logging.log4j.jul.LogManager' +} + +dependencies { + // JUnit Jupiter API and TestEngine implementation + testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") + testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") + + // If you also want to support JUnit 3 and JUnit 4 tests + testCompile("junit:junit:${junit4Version}") + testRuntime("org.junit.vintage:junit-vintage-engine:${junitVintageVersion}") + + // To avoid compiler warnings about @API annotations in JUnit code + testCompileOnly('org.apiguardian:apiguardian-api:1.0.0') + + // To use Log4J's LogManager + testRuntime("org.apache.logging.log4j:log4j-core:${log4jVersion}") + testRuntime("org.apache.logging.log4j:log4j-jul:${log4jVersion}") + + // Only needed to run tests in an (IntelliJ) IDE(A) that bundles an older version + testRuntime("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}") + + testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.jetbrains', name: 'annotations', version: '15.0' + + compile group: 'com.google.guava', name: 'guava', version: '23.5-jre' + testCompile group: 'org.mockito', name: 'mockito-all', version: '2.0.2-beta' +} + +task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '4.3.1' +} \ No newline at end of file diff --git a/HW1-Lazy/gradle/wrapper/gradle-wrapper.jar b/HW1-Lazy/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..01b8bf6 Binary files /dev/null and b/HW1-Lazy/gradle/wrapper/gradle-wrapper.jar differ diff --git a/HW1-Lazy/gradle/wrapper/gradle-wrapper.properties b/HW1-Lazy/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9203d44 --- /dev/null +++ b/HW1-Lazy/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Feb 27 02:20:16 MSK 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip diff --git a/HW1-Lazy/gradlew b/HW1-Lazy/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/HW1-Lazy/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/HW1-Lazy/gradlew.bat b/HW1-Lazy/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/HW1-Lazy/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/HW1-Lazy/settings.gradle b/HW1-Lazy/settings.gradle new file mode 100644 index 0000000..3ea267a --- /dev/null +++ b/HW1-Lazy/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'HW1-Lazy' + diff --git a/HW1-Lazy/src/main/java/ru/spbau/mit/kirakosian/Lazy.java b/HW1-Lazy/src/main/java/ru/spbau/mit/kirakosian/Lazy.java new file mode 100644 index 0000000..e84252a --- /dev/null +++ b/HW1-Lazy/src/main/java/ru/spbau/mit/kirakosian/Lazy.java @@ -0,0 +1,19 @@ +package ru.spbau.mit.kirakosian; + +/** + * Interface to store exactly one value and provide lazy evaluations. It means, that target + * value will be evaluated only one time, on the first get method call. + * + * This class in some way similar to Singleton pattern. + * + * @param type of the stored element. + */ +public interface Lazy { + + /** + * Returns stored in Lazy value. Evaluates this value if it is not known yet, + * otherwise returns already evaluated value. + */ + T get(); + +} diff --git a/HW1-Lazy/src/main/java/ru/spbau/mit/kirakosian/LazyFactory.java b/HW1-Lazy/src/main/java/ru/spbau/mit/kirakosian/LazyFactory.java new file mode 100644 index 0000000..5e0dff2 --- /dev/null +++ b/HW1-Lazy/src/main/java/ru/spbau/mit/kirakosian/LazyFactory.java @@ -0,0 +1,97 @@ +package ru.spbau.mit.kirakosian; + +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +/** + * Factory class to produce different ru.spbau.mit.kirakosian.Lazy classes. Unsynchronized and synchronized versions + * are supported. + */ +@SuppressWarnings("WeakerAccess") +public class LazyFactory { + + /** + * Basic method to create unsynchronized lazy class from the given supplier. + * + * @param supplier supplier to get element from. + * @param type of the stored in ru.spbau.mit.kirakosian.Lazy element + * @return new concurrent unsafe ru.spbau.mit.kirakosian.Lazy instance + */ + public static Lazy createUnsynchronizedLazy(final Supplier supplier) { + // there was a variant to use anonymous class instead of inner static, + // but with this implementation our code is a bit more flexible. For example, if + // we want to support something else instead of supplier we may use the same class, + // but with the different constructor. + return new LazyUnsync<>(supplier); + } + + /** + * Basic method to create synchronized lazy class from the given supplier. + * + * @param supplier supplier to get element from. + * @param type of the stored in ru.spbau.mit.kirakosian.Lazy element + * @return new concurrent safe ru.spbau.mit.kirakosian.Lazy instance + */ + public static Lazy createSynchronizedLazy(final Supplier supplier) { + return new LazySync<>(supplier); + } + + /** + * Basic ru.spbau.mit.kirakosian.Lazy implementation. This class is not recommended to use in concurrent programs, + * use LazySync implementation instead. + * + * @param type of the stored value + */ + private static class LazyUnsync implements Lazy { + + @Nullable + private Supplier supplier; + private T value; + + LazyUnsync(@Nullable final Supplier supplier) { + this.supplier = supplier; + } + + // as i see, if there is no description the one from the interface will be shown + // it looks more beautiful then the link in my opinion + @Override + public T get() { + if (supplier != null) { + value = supplier.get(); + supplier = null; + } + return value; + } + } + + /** + * Synchronized version of LazyUnsync class. Supports correct work with many threads. + * + * @param type the of stored value. + */ + private static class LazySync implements Lazy { + + @Nullable + private Supplier supplier; + private T value; + + LazySync(@Nullable final Supplier supplier) { + this.supplier = supplier; + } + + @Override + public T get() { + if (supplier != null) { + synchronized (this) { + if (supplier != null) { // check that we have not done this section yet + value = supplier.get(); + supplier = null; + } + } + } + return value; + } + } + +} diff --git a/HW1-Lazy/src/test/java/ru/spbau/mit/kirakosian/CounterSupplier.java b/HW1-Lazy/src/test/java/ru/spbau/mit/kirakosian/CounterSupplier.java new file mode 100644 index 0000000..2ae0e74 --- /dev/null +++ b/HW1-Lazy/src/test/java/ru/spbau/mit/kirakosian/CounterSupplier.java @@ -0,0 +1,41 @@ +package ru.spbau.mit.kirakosian; + +import java.util.function.Supplier; + +/** + * Simple supplier that returns target value on every get method call. + * + * In addition, counts number of get method calls. + * + * @param type of the stored value. + */ +@SuppressWarnings("WeakerAccess") +class CounterSupplier implements Supplier { + + private int counter; + private final T value; + + /** + * Constructs supplier with the target value. + * + * @param value value to return on get method call. + */ + public CounterSupplier(final T value) { + this.value = value; + } + + /** + * Access to the counter. + * + * @return number of get method calls. + */ + public int counter() { + return counter; + } + + @Override + public T get() { + counter++; + return value; + } +} diff --git a/HW1-Lazy/src/test/java/ru/spbau/mit/kirakosian/LazyFactoryTest.java b/HW1-Lazy/src/test/java/ru/spbau/mit/kirakosian/LazyFactoryTest.java new file mode 100644 index 0000000..a6da585 --- /dev/null +++ b/HW1-Lazy/src/test/java/ru/spbau/mit/kirakosian/LazyFactoryTest.java @@ -0,0 +1,73 @@ +package ru.spbau.mit.kirakosian; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.util.LinkedList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@SuppressWarnings("FieldCanBeLocal") +class LazyFactoryTest { + + private final int threadsNumber = 10; + private final int testsNumber = 100; + + @Test + void testCreateUnsynchronizedLazye() { + final CounterSupplier stringSupplier = new CounterSupplier<>("hello"); + final CounterSupplier nullSupplier = new CounterSupplier<>(null); + + assertEquals(stringSupplier.counter(), 0); + assertEquals(nullSupplier.counter(), 0); + + final Lazy stringLazy = LazyFactory.createUnsynchronizedLazy(stringSupplier); + final Lazy nullLazy = LazyFactory.createUnsynchronizedLazy(nullSupplier); + + assertEquals("hello", stringLazy.get()); + assertEquals("hello", stringLazy.get()); + assertEquals("hello", stringLazy.get()); + + assertNull(nullLazy.get()); + assertNull(nullLazy.get()); + assertNull(nullLazy.get()); + assertNull(nullLazy.get()); + + assertEquals(1, stringSupplier.counter()); + assertEquals(1, nullSupplier.counter()); + } + + @Test + void testCreateSynchronizedLazy() { + for (int i = 0; i < testsNumber; i++) { + try { + oneCreateSynchronizedLazyTest("hello"); + oneCreateSynchronizedLazyTest(null); + } catch (@NotNull final InterruptedException e) { + // no idea what to do here... It is not a failure + } + } + } + + private void oneCreateSynchronizedLazyTest(final String testValue) throws InterruptedException { + final CounterSupplier supplier = new CounterSupplier<>(testValue); + final Lazy lazy = LazyFactory.createSynchronizedLazy(supplier); + + final List tasks = new LinkedList<>(); + for (int i = 0; i < threadsNumber; i++) { + tasks.add(new Thread(() -> assertEquals(testValue, lazy.get()))); + } + + for (final Thread task : tasks) { + task.start(); + } + + for (final Thread task : tasks) { + task.join(); + } + assertEquals(supplier.counter(), 1); + } + +} \ No newline at end of file diff --git a/ThreadPool/build.gradle b/ThreadPool/build.gradle new file mode 100644 index 0000000..80a163a --- /dev/null +++ b/ThreadPool/build.gradle @@ -0,0 +1,92 @@ +group 'JavaHW' +version '1.0-SNAPSHOT' + +group 'JavaHW' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.2' + classpath 'com.google.guava:guava:23.5-jre' + classpath 'org.mockito:mockito-all:2.0.2-beta' + } +} + +repositories { + mavenCentral() +} + +ext.junit4Version = '4.12' +ext.junitVintageVersion = '4.12.2' +ext.junitPlatformVersion = '1.0.2' +ext.junitJupiterVersion = '5.0.2' +ext.log4jVersion = '2.9.0' + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'org.junit.platform.gradle.plugin' + +jar { + baseName = 'junit5-gradle-consumer' + version = '1.0.0-SNAPSHOT' +} + +compileTestJava { + sourceCompatibility = 1.9 + targetCompatibility = 1.9 + options.compilerArgs += '-parameters' +} + +junitPlatform { + // platformVersion '1.0.2' + filters { + engines { + // include 'junit-jupiter', 'junit-vintage' + // exclude 'custom-engine' + } + tags { + // include 'fast' + exclude 'slow' + } + // includeClassNamePattern '.*Test' + } + // configurationParameter 'junit.jupiter.conditions.deactivate', '*' + // enableStandardTestTask true + // reportsDir file('build/test-results/junit-platform') // this is the default + logManager 'org.apache.logging.log4j.jul.LogManager' +} + +dependencies { + // JUnit Jupiter API and TestEngine implementation + testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}") + testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}") + + // If you also want to support JUnit 3 and JUnit 4 tests + testCompile("junit:junit:${junit4Version}") + testRuntime("org.junit.vintage:junit-vintage-engine:${junitVintageVersion}") + + // To avoid compiler warnings about @API annotations in JUnit code + testCompileOnly('org.apiguardian:apiguardian-api:1.0.0') + + // To use Log4J's LogManager + testRuntime("org.apache.logging.log4j:log4j-core:${log4jVersion}") + testRuntime("org.apache.logging.log4j:log4j-jul:${log4jVersion}") + + // Only needed to run tests in an (IntelliJ) IDE(A) that bundles an older version + testRuntime("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}") + + testCompile group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0' + compile group: 'org.jetbrains', name: 'annotations', version: '15.0' + + compile group: 'com.google.guava', name: 'guava', version: '23.5-jre' + testCompile group: 'org.mockito', name: 'mockito-all', version: '2.0.2-beta' +} + +task wrapper(type: Wrapper) { + description = 'Generates gradlew[.bat] scripts' + gradleVersion = '4.6' +} diff --git a/ThreadPool/gradle/wrapper/gradle-wrapper.jar b/ThreadPool/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..a5fe1cb Binary files /dev/null and b/ThreadPool/gradle/wrapper/gradle-wrapper.jar differ diff --git a/ThreadPool/gradle/wrapper/gradle-wrapper.properties b/ThreadPool/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1c995df --- /dev/null +++ b/ThreadPool/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Mar 02 18:03:15 MSK 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/ThreadPool/gradlew b/ThreadPool/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/ThreadPool/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/ThreadPool/gradlew.bat b/ThreadPool/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/ThreadPool/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ThreadPool/settings.gradle b/ThreadPool/settings.gradle new file mode 100644 index 0000000..abed208 --- /dev/null +++ b/ThreadPool/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'ThreadPool' + diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/BlockingQueue.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/BlockingQueue.java new file mode 100644 index 0000000..0544818 --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/BlockingQueue.java @@ -0,0 +1,54 @@ +package com.spbau.mit.kirakosian.threadPool; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * This class provides safe concurrent queue. + *

+ * In other words, this class is a wrapper around simple queue class, + * but in addition every action in concurrent program will be safe, that means, + * that you are not able to override any values in your queue, or to take + * the same value twice. + * + * @param type of the queue. + */ +@SuppressWarnings("WeakerAccess") +public class BlockingQueue { + + private final Queue tasks = new LinkedList<>(); + + /** + * Adds new item to the end of the queue. + * + * @param item item to add into queue. + */ + public void push(final T item) { + synchronized (tasks) { + tasks.add(item); + tasks.notify(); + } + } + + /** + * The method extracts top item from the queue. + * + * @return top item of the queue. + */ + @Nullable + public T pop() { + synchronized (tasks) { + while (tasks.isEmpty()) { + try { + tasks.wait(); + } catch (@NotNull final InterruptedException e) { + // do nothing + } + } + return tasks.poll(); + } + } +} \ No newline at end of file diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/LightFuture.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/LightFuture.java new file mode 100644 index 0000000..2cc4c80 --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/LightFuture.java @@ -0,0 +1,39 @@ +package com.spbau.mit.kirakosian.threadPool; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * This interface provides methods for convenient tracking of your tasks. + *

+ * For example, you may check if execution of the task already has ended with + * {@link LightFuture#isReady()}, get result of the calculation at any time (task + * may be not finished yet) or add new tasks with dependency on the result of the + * calculation. + * + * @param + */ +public interface LightFuture extends Supplier { + + /** + * The method checks if any the task was already executed. Task may end + * with an error, this method still will return true. + * + * @return true if task was finished already and false otherwise. + */ + boolean isReady(); + + /** + * The method adds new task to the threadPool, which depends on the result of + * the calculation. That means, that task cannot be evaluated until its parent task + * is not ready. + *

+ * The method takes function f, and applies it to the result of the calculation. + * + * @param f function to apply + * @param type of the function result + * @return new LightFuture task, which was already added to the pool. + */ + LightFuture thenApply(Function f); + +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPool.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPool.java new file mode 100644 index 0000000..b30ba29 --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPool.java @@ -0,0 +1,47 @@ +package com.spbau.mit.kirakosian.threadPool; + +import java.util.function.Supplier; + +// In general, we may be able to work with any Task (Runnable interface), +// but here we want to work with LightFuture tasks only to provide user +// convenient interface to track tasks progress e.t.c. + +/** + * This interface provides base thread pool interface, which means that you are + * able to add new tasks to the pool and to call shutdown method, which turns the pool off. + *

+ * In addition, the pool provides convenient interface to track your tasks progress + * with the LightFuture interface. Every task will be wrapped in this interface. + */ +public interface ThreadPool { + + /** + * The method adds new task to the thread pool. Added tasks is equals to one "get" + * calculation of the supplier. + *

+ * In case of trying to add new task after shutdown ThreadPoolIsTurnedDownException + * will be thrown. + * + * @param supplier supplier to create task from. + * @param type of the result of calculation. + * @return new LightFuture object to track your task state. + */ + LightFuture addTask(Supplier supplier); + + /** + * Tells pool to shutdown. After calling this method no more tasks + * will be applied to the pool. All tasks, that are already stored + * will be finished. + *

+ * In case of trying to add new task after shutdown ThreadPoolIsTurnedDownException + * will be thrown. + */ + void shutdown(); + + /** + * This method stops your main thread until all tasks in poll will be executed + * and calls shutdown method of the pool, therefore after calling this method + * you might be sure that all tasks added to the pool are already finished. + */ + void waitWithShutDown(); +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPoolFactory.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPoolFactory.java new file mode 100644 index 0000000..ea243b3 --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPoolFactory.java @@ -0,0 +1,15 @@ +package com.spbau.mit.kirakosian.threadPool; + +/** + * Class with factory methods to create ThreadPool interface classes. + *

+ * By this class we hide out {@link ThreadPoolImpl} class and show only + * {@link ThreadPool} interface. + */ +@SuppressWarnings("WeakerAccess") +public class ThreadPoolFactory { + + public static ThreadPool initThreadPool(final int threadsNumber) { + return new ThreadPoolImpl(threadsNumber); + } +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPoolImpl.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPoolImpl.java new file mode 100644 index 0000000..f1ca7fa --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/ThreadPoolImpl.java @@ -0,0 +1,238 @@ +package com.spbau.mit.kirakosian.threadPool; + +import com.spbau.mit.kirakosian.threadPool.exceptions.LightExecutionException; +import com.spbau.mit.kirakosian.threadPool.exceptions.TaskIsReadyAlreadyException; +import com.spbau.mit.kirakosian.threadPool.exceptions.ThreadPoolIsTurnedDownException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * {@link ThreadPool} + *

+ * Basic ThreadPool interface implementation. + *

+ * This class supports safe work in concurrent environment. Class is not visible outside + * from the package, therefore it is possible to work with only with public methods + * of {@link ThreadPool} interface. + *

+ * In general, this pool supports only tasks, which implements LightFuture interface, + * but there is an easy way to work with any Runnable tasks, therefore blocking queue + * inside of the class stores any kind of Runnable classes. + */ +class ThreadPoolImpl implements ThreadPool { + + @Nullable + private final Supplier POISON_PILL = () -> null; + @Nullable + private final Task POISON_PILL_TASK = new Task<>(POISON_PILL); + + // Locks + private final Object shutDownLock = new Object(); + + private final BlockingQueue tasks = new BlockingQueue<>(); + + private final int threadsNumber; + private boolean isWorking = true; + private int threadsStillWorking; + + + ThreadPoolImpl(final int threadsNumber) { + this.threadsNumber = threadsNumber; + threadsStillWorking = threadsNumber; + final List threads = new LinkedList<>(); + for (int i = 0; i < threadsNumber; i++) { + threads.add(new Thread(new ThreadTask())); + } + for (final Thread thread : threads) { + thread.setDaemon(true); + thread.start(); + } + } + + /** + * Poison pill pattern is used for {@link ThreadPool#shutdown()} implementation. + */ + @Override + public synchronized void shutdown() { + if (isWorking) { // otherwise, we may do this section twice + for (int i = 0; i < threadsNumber; i++) { + addTask(POISON_PILL); + } + } + isWorking = false; + } + + /** + * {@link ThreadPool#waitWithShutDown()} + */ + @Override + public void waitWithShutDown() { + shutdown(); + synchronized (shutDownLock) { + while (threadsStillWorking != 0) { + try { + shutDownLock.wait(); + } catch (@NotNull final InterruptedException e) { + // do nothing + } + } + } + } + + /** + * {@link ThreadPool#addTask(Supplier)} + */ + @Nullable + @Override + public synchronized LightFuture addTask(final Supplier supplier) { + if (!isWorking) { + throw new ThreadPoolIsTurnedDownException(); + } + if (supplier == POISON_PILL) { + tasks.push(POISON_PILL_TASK); + return null; + } + final Task task = new Task<>(supplier); + tasks.push(task); + return task; + } + + /** + * Default task for every thread in the thread pool. + *

+ * Taking tasks from the queue until thread pool is working. + */ + private class ThreadTask implements Runnable { + + @Override + public void run() { + while (true) { + final Runnable task = tasks.pop(); + if (task == POISON_PILL_TASK) { + synchronized (shutDownLock) { + threadsStillWorking--; + if (threadsStillWorking == 0) { + shutDownLock.notifyAll(); + } + } + break; + } + if (task != null) { // there was a warning, so it is better to check for sure + task.run(); + } + } + } + } + + /** + * Simple LightFuture interface implementation, that may be used inside + * ThreadPoolImpl class. This class is not visible from outside, therefore you are + * not able to work with this class directly. + *

+ * Public interface of this class is safe in concurrent environment. + *

+ * Talking about not visible for user interface (such as method run, that is not + * visible with the interface LightFuture) it is expected, that programmer + * works safe with this methods (for example, calls run method twice). If there was + * any mistake in the code, TaskIsReadyAlreadyException is expected to be thrown. + *

+ * If an exception occurred during the task execution this exception will be stored + * inside of the class and passed to every future calculations (thenApply method). + * In case of trying to get result of the failed calculation or execute any + * corresponded on the answer task LightExecutionException will be thrown. + * + * @param return type of execution + */ + private class Task implements LightFuture, Runnable { + + private final Supplier supplier; + + private boolean ready; + private T value; + private Exception error; + + private Task(final Supplier supplier) { + this.supplier = supplier; + } + + /** + * Task is ready if it execution was ended by any thread. It does not matter + * either it was successful or not (ended with an exception). + *

+ * {@link LightFuture#isReady()} + * + * @return true if task was already completed and false otherwise. + */ + @Override + public boolean isReady() { + return ready; + } + + /** + * Returns the value of the calculation. If calculation is not ready yet, waits + * for its end and then returns the result. + *

+ * If there was an error during execution LightExecutionException with + * cause of the fail inside will be thrown. + *

+ * {@link LightFuture#get()} + * + * @return result of the calculation. + */ + @Override + public T get() { + synchronized (this) { + while (!ready) { // waiting for result + try { + wait(); + } catch (@NotNull final InterruptedException e) { + // nothing to do here + } + } + } + if (error != null) { + throw new LightExecutionException(error); + } + return value; + } + + + /** + * {@link LightFuture#thenApply(Function)} + */ + @Nullable + @Override + public LightFuture thenApply(@NotNull final Function f) { + final Supplier supplier = () -> f.apply(Task.this.get()); + return addTask(supplier); + } + + /** + * Inner method of class. Should not be visible from outside (of course it is + * possible to call this method with cast of this object to Runnable, + * but let us think that it is a user problem). + *

+ * This method is called, when thread from the pool staring to execute this task. + * Runnable interface is supported for this very reason. + */ + @Override + public synchronized void run() { + if (!ready) { // we may execute every task only one time + try { + value = supplier.get(); + } catch (@NotNull final Exception e) { + error = e; + } + ready = true; + notifyAll(); + } else { + throw new TaskIsReadyAlreadyException(); // should never happen, cause it is an inner method + } + } + } +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/Utils.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/Utils.java new file mode 100644 index 0000000..866ecdc --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/Utils.java @@ -0,0 +1,15 @@ +package com.spbau.mit.kirakosian.threadPool; + +import org.jetbrains.annotations.NotNull; + +public class Utils { + + public static void sleep(final int time) { + try { + Thread.sleep(time); + } catch (@NotNull final InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/LightExecutionException.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/LightExecutionException.java new file mode 100644 index 0000000..c306037 --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/LightExecutionException.java @@ -0,0 +1,17 @@ +package com.spbau.mit.kirakosian.threadPool.exceptions; + +/** + * This class provides exception to be thrown in case of calling get method + * of calculation, which was failed with th exception. + */ +public class LightExecutionException extends RuntimeException { + + @SuppressWarnings("unused") + public LightExecutionException() { + super(); + } + + public LightExecutionException(final Exception error) { + super(error); + } +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/TaskIsReadyAlreadyException.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/TaskIsReadyAlreadyException.java new file mode 100644 index 0000000..4aa31ea --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/TaskIsReadyAlreadyException.java @@ -0,0 +1,13 @@ +package com.spbau.mit.kirakosian.threadPool.exceptions; + +import com.spbau.mit.kirakosian.threadPool.LightFuture; + +/** + * This exception may be thrown if implementation of {@link LightFuture} + * interface methods was not concurrent safe. + *

+ * For example, if tasks method "run" was called more then one time. + */ +public class TaskIsReadyAlreadyException extends RuntimeException { + +} diff --git a/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/ThreadPoolIsTurnedDownException.java b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/ThreadPoolIsTurnedDownException.java new file mode 100644 index 0000000..c8ca245 --- /dev/null +++ b/ThreadPool/src/main/java/com/spbau/mit/kirakosian/threadPool/exceptions/ThreadPoolIsTurnedDownException.java @@ -0,0 +1,8 @@ +package com.spbau.mit.kirakosian.threadPool.exceptions; + +/** + * This exception is thrown in case of trying to add new task in the pool, that + * was already turned off. + */ +public class ThreadPoolIsTurnedDownException extends RuntimeException { +} diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/ThreadPoolFactoryTest.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/ThreadPoolFactoryTest.java new file mode 100644 index 0000000..f5cb681 --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/ThreadPoolFactoryTest.java @@ -0,0 +1,199 @@ +package com.spbau.mit.kirakosian; + +import com.spbau.mit.kirakosian.suppliers.*; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import com.spbau.mit.kirakosian.threadPool.LightFuture; +import com.spbau.mit.kirakosian.threadPool.ThreadPool; +import com.spbau.mit.kirakosian.threadPool.ThreadPoolFactory; +import com.spbau.mit.kirakosian.threadPool.Utils; +import com.spbau.mit.kirakosian.threadPool.exceptions.LightExecutionException; +import com.spbau.mit.kirakosian.threadPool.exceptions.ThreadPoolIsTurnedDownException; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SuppressWarnings("FieldCanBeLocal") +class ThreadPoolFactoryTest { + + private final int testsNumber = 100; + private final int maxThreadsNumber = 20; + + /** + * This test checks if every task was executed exactly one time. + */ + @Test + void testCallEveryTaskOnce() { + for (int t = 0; t < testsNumber; t++) { + final ThreadPool pool = ThreadPoolFactory.initThreadPool(3); + final List list = new LinkedList<>(); + + for (int i = 0; i < 10; i++) { + final CounterSupplier dummy = new CounterSupplier(); + list.add(dummy); + pool.addTask(dummy); + } + + pool.waitWithShutDown(); + + assertEquals((t + 1) * 10, list.get(0).getCounter()); + for (final CounterSupplier counterSupplier : list) { + assertEquals(1, counterSupplier.getMyCounter()); + } + } + } + + /** + * This test checks if there at least n threads int the pool. + *

+ * Initialises n - 1 thread with waiting tasks and notifying them with the last one. + * If there are not enough threads in the pool, test will stuck, therefore there is + * a timeout for test execution. + */ + @Test + void testALotOfThreads() { + for (int i = 0; i < maxThreadsNumber; i++) { + testNThreads(i * i / 2); + } + } + + private void testNThreads(final int threadsNumber) { + + final Thread timer = new Thread(() -> { + Utils.sleep(5000); + synchronized (this) { + notify(); + } + }); + timer.start(); + + final ThreadPool pool = ThreadPoolFactory.initThreadPool(threadsNumber); + final List> list = new LinkedList<>(); + + for (int i = 0; i < threadsNumber - 1; i++) { + list.add(pool.addTask(new WaitSupplier())); + } + pool.addTask(new SleepSupplier()); + pool.addTask(new NotifySupplier()); + + new Thread(() -> { + pool.waitWithShutDown(); + synchronized (this) { + notify(); + } + }).start(); + + synchronized (this) { + try { + // there must be loop, but i cannot change variable + // for it in lambda... What should I do with it? + // Here I will hope that at least one test will fail. + wait(); + } catch (@NotNull final InterruptedException e) { + // do nothing + } + // Am i right that timer cannot still work? + // In case thread was set on this one immediately? + if (!timer.isAlive()) { + fail("Out of time."); + } + } + + for (final LightFuture lightFuture : list) { + assertTrue(lightFuture.isReady()); + } + } + + @Test + void testIsReady() { + final ThreadPool pool = ThreadPoolFactory.initThreadPool(3); + final LightFuture future = pool.addTask(new SleepSupplier(250)); + pool.addTask(new SleepSupplier()); + + assertFalse(future.isReady()); + new Thread(() -> assertFalse(future.isReady())).start(); + Utils.sleep(500); + assertTrue(future.isReady()); + new Thread(() -> assertTrue(future.isReady())).start(); + } + + @Test + void testGet() { + final ThreadPool pool = ThreadPoolFactory.initThreadPool(3); + + final ArrayList> list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + list.add(pool.addTask(new IntegerSupplier(i * i))); + } + + final Runnable r = () -> { + for (int i = 0; i < 10; i++) { + assertEquals(Integer.valueOf(i * i), list.get(i).get()); + } + }; + new Thread(r).start(); + new Thread(r).start(); + new Thread(r).start(); + r.run(); + } + + @Test + void testShutDown() { + final ThreadPool pool = ThreadPoolFactory.initThreadPool(3); + final ArrayList> list = new ArrayList<>(); + + list.add(pool.addTask(new SleepSupplier())); + list.add(pool.addTask(new SleepSupplier())); + list.add(pool.addTask(new SleepSupplier())); + + pool.shutdown(); + + assertThrows(ThreadPoolIsTurnedDownException.class, + () -> pool.addTask(new IntegerSupplier(1))); + + pool.waitWithShutDown(); + + for (final LightFuture future : list) { + assertTrue(future.isReady()); + } + } + + @Test + void testExceptions() { + final ThreadPool pool = ThreadPoolFactory.initThreadPool(3); + final LightFuture task = pool.addTask(() -> { + throw new RuntimeException(); + }); + + final Runnable check = () -> { + assertThrows(LightExecutionException.class, task::get); + assertThrows(LightExecutionException.class, + () -> task.thenApply(o -> 1).get()); + }; + + check.run(); + new Thread(check).start(); + new Thread(check).start(); + } + + @Test + void testThenApply() { + final ThreadPool pool = ThreadPoolFactory.initThreadPool(3); + + final LightFuture task = pool.addTask(() -> 1); + for (int i = 0; i < 5; i++) { + pool.addTask(new SleepSupplier()); + } + + final Runnable r = () -> assertEquals(Integer.valueOf(2), task.thenApply(n -> n + 1).get()); + r.run(); + new Thread(r).start(); + new Thread(r).start(); + new Thread(r).start(); + + pool.waitWithShutDown(); + } +} \ No newline at end of file diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/BaseSupplier.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/BaseSupplier.java new file mode 100644 index 0000000..f7dc459 --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/BaseSupplier.java @@ -0,0 +1,14 @@ +package com.spbau.mit.kirakosian.suppliers; + +import java.util.function.Supplier; + +/** + * Base class for some simple com.spbau.mit.kirakosian.suppliers, which may be used in tests. + * + * @param type of stored value. + */ +abstract class BaseSupplier implements Supplier { + + static final Object commonLock = new Object(); + +} diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/CounterSupplier.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/CounterSupplier.java new file mode 100644 index 0000000..30185c3 --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/CounterSupplier.java @@ -0,0 +1,30 @@ +package com.spbau.mit.kirakosian.suppliers; + +import org.jetbrains.annotations.Nullable; + +public class CounterSupplier extends BaseSupplier { + + private static int counter; + private int myCounter; + + public int getCounter() { + return counter; + } + + @Nullable + @Override + public Void get() { + synchronized (this) { + myCounter++; + } + synchronized (commonLock) { + counter++; + } + return null; + } + + public int getMyCounter() { + return myCounter; + } + +} diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/IntegerSupplier.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/IntegerSupplier.java new file mode 100644 index 0000000..bee9798 --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/IntegerSupplier.java @@ -0,0 +1,15 @@ +package com.spbau.mit.kirakosian.suppliers; + +public class IntegerSupplier extends BaseSupplier { + + private final Integer n; + + public IntegerSupplier(final int n) { + this.n = n; + } + + @Override + public Integer get() { + return n; + } +} diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/NotifySupplier.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/NotifySupplier.java new file mode 100644 index 0000000..3c885ae --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/NotifySupplier.java @@ -0,0 +1,15 @@ +package com.spbau.mit.kirakosian.suppliers; + +import org.jetbrains.annotations.Nullable; + +public class NotifySupplier extends BaseSupplier { + + @Nullable + @Override + public Void get() { + synchronized (commonLock) { + commonLock.notifyAll(); + } + return null; + } +} diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/SleepSupplier.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/SleepSupplier.java new file mode 100644 index 0000000..bcf2fc8 --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/SleepSupplier.java @@ -0,0 +1,24 @@ +package com.spbau.mit.kirakosian.suppliers; + +import org.jetbrains.annotations.Nullable; +import com.spbau.mit.kirakosian.threadPool.Utils; + +public class SleepSupplier extends BaseSupplier { + + private int time = 100; + + public SleepSupplier() { + + } + + public SleepSupplier(final int time) { + this.time = time; + } + + @Nullable + @Override + public Void get() { + Utils.sleep(time); + return null; + } +} diff --git a/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/WaitSupplier.java b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/WaitSupplier.java new file mode 100644 index 0000000..d1a84bd --- /dev/null +++ b/ThreadPool/src/test/java/com/spbau/mit/kirakosian/suppliers/WaitSupplier.java @@ -0,0 +1,21 @@ +package com.spbau.mit.kirakosian.suppliers; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class WaitSupplier extends BaseSupplier { + + @Nullable + @Override + public Void get() { + synchronized (commonLock) { + try { + commonLock.wait(); + } catch (@NotNull final InterruptedException e) { + //do nothing + } + } + return null; + } + +}