├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── techyourchance │ │ └── multithreading │ │ ├── DefaultConfiguration.java │ │ ├── MainActivity.java │ │ ├── MyApplication.java │ │ ├── common │ │ ├── BaseFragment.java │ │ ├── BaseObservable.java │ │ ├── ScreensNavigator.java │ │ ├── ToolbarManipulator.java │ │ ├── dependencyinjection │ │ │ ├── ApplicationCompositionRoot.java │ │ │ └── PresentationCompositionRoot.java │ │ └── math │ │ │ └── MathUtils.java │ │ ├── demonstrations │ │ ├── atomicity │ │ │ └── AtomicityDemonstrationFragment.java │ │ ├── bestjavaimplementation │ │ │ ├── ComputeFactorialUseCase.java │ │ │ ├── MyBlockingQueue.java │ │ │ └── ProducerConsumerBenchmarkUseCase.java │ │ ├── customhandler │ │ │ └── CustomHandlerDemonstrationFragment.java │ │ ├── designasynctask │ │ │ ├── DesignWithAsyncTaskDemonstrationFragment.java │ │ │ ├── MyBlockingQueue.java │ │ │ └── ProducerConsumerBenchmarkUseCase.java │ │ ├── designcoroutines │ │ │ ├── DesignWithCoroutinesDemonstrationFragment.kt │ │ │ ├── MyBlockingQueue.kt │ │ │ └── ProducerConsumerBenchmarkUseCase.kt │ │ ├── designrxjava │ │ │ ├── DesignWithRxJavaDemonstrationFragment.java │ │ │ ├── MyBlockingQueue.java │ │ │ └── ProducerConsumerBenchmarkUseCase.java │ │ ├── designthread │ │ │ ├── DesignWithThreadsDemonstrationFragment.java │ │ │ ├── MyBlockingQueue.java │ │ │ └── ProducerConsumerBenchmarkUseCase.java │ │ ├── designthreadpool │ │ │ ├── DesignWithThreadPoolDemonstrationFragment.java │ │ │ ├── MyBlockingQueue.java │ │ │ └── ProducerConsumerBenchmarkUseCase.java │ │ ├── designthreadposter │ │ │ ├── DesignWithThreadPosterDemonstrationFragment.java │ │ │ ├── MyBlockingQueue.java │ │ │ └── ProducerConsumerBenchmarkUseCase.java │ │ ├── synchronization │ │ │ └── SynchronizationDemonstration.java │ │ ├── threadwait │ │ │ └── ThreadWaitDemonstrationFragment.java │ │ ├── uihandler │ │ │ └── UiHandlerDemonstrationFragment.java │ │ ├── uithread │ │ │ └── UiThreadDemonstrationFragment.java │ │ └── visibility │ │ │ └── VisibilityDemonstration.java │ │ ├── exercises │ │ ├── exercise1 │ │ │ └── Exercise1Fragment.java │ │ ├── exercise10 │ │ │ ├── ComputeFactorialUseCase.kt │ │ │ ├── Exercise10Fragment.kt │ │ │ └── tips.txt │ │ ├── exercise2 │ │ │ └── Exercise2Fragment.java │ │ ├── exercise3 │ │ │ └── Exercise3Fragment.java │ │ ├── exercise4 │ │ │ └── Exercise4Fragment.java │ │ ├── exercise5 │ │ │ └── Exercise5Fragment.java │ │ ├── exercise6 │ │ │ └── Exercise6Fragment.java │ │ ├── exercise7 │ │ │ ├── ComputeFactorialUseCase.java │ │ │ └── Exercise7Fragment.java │ │ ├── exercise8 │ │ │ ├── ComputeFactorialUseCase.java │ │ │ └── Exercise8Fragment.java │ │ └── exercise9 │ │ │ ├── ComputeFactorialUseCase.java │ │ │ └── Exercise9Fragment.java │ │ ├── home │ │ ├── HomeArrayAdapter.java │ │ ├── HomeFragment.java │ │ └── ScreenReachableFromHome.java │ │ └── solutions │ │ ├── exercise1 │ │ └── SolutionExercise1Fragment.java │ │ ├── exercise10 │ │ ├── ComputeFactorialUseCase.kt │ │ └── Exercise10Fragment.kt │ │ ├── exercise2 │ │ └── SolutionExercise2Fragment.java │ │ ├── exercise3 │ │ └── SolutionExercise3Fragment.java │ │ ├── exercise4 │ │ └── SolutionExercise4Fragment.java │ │ ├── exercise5 │ │ └── SolutionExercise5Fragment.java │ │ ├── exercise6 │ │ ├── ComputeFactorialUseCase.java │ │ └── SolutionExercise6Fragment.java │ │ ├── exercise7 │ │ ├── ComputeFactorialUseCase.java │ │ └── SolutionExercise7Fragment.java │ │ ├── exercise8 │ │ ├── ComputeFactorialUseCase.java │ │ └── SolutionExercise8Fragment.java │ │ └── exercise9 │ │ ├── ComputeFactorialUseCase.java │ │ └── SolutionExercise9Fragment.java │ └── res │ ├── drawable-hdpi │ └── ic_arrow_back.png │ ├── drawable-mdpi │ └── ic_arrow_back.png │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ └── ic_arrow_back.png │ ├── drawable-xxhdpi │ └── ic_arrow_back.png │ ├── drawable-xxxhdpi │ └── ic_arrow_back.png │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_main.xml │ ├── fragment_atomicity_demonstration.xml │ ├── fragment_custom_looper_demonstration.xml │ ├── fragment_design_with_coroutines_demonstration.xml │ ├── fragment_design_with_thread_demonstration.xml │ ├── fragment_design_with_thread_pool_demonstration.xml │ ├── fragment_exercise_1.xml │ ├── fragment_exercise_10.xml │ ├── fragment_exercise_2.xml │ ├── fragment_exercise_3.xml │ ├── fragment_exercise_4.xml │ ├── fragment_exercise_5.xml │ ├── fragment_exercise_6.xml │ ├── fragment_exercise_7.xml │ ├── fragment_exercise_8.xml │ ├── fragment_exercise_9.xml │ ├── fragment_home.xml │ ├── fragment_thread_wait_demonstration.xml │ ├── fragment_ui_handler_demonstration.xml │ ├── fragment_ui_thread_demonstration.xml │ └── list_item_screen_reachable_from_home.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── fragmenthelper ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── techyourchance │ │ └── fragmenthelper │ │ ├── FragmentContainerWrapper.java │ │ ├── FragmentHelper.java │ │ └── HierarchicalFragment.java │ └── res │ └── values │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── release.keystore └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #built application files 3 | *.apk 4 | *.ap_ 5 | 6 | # files for the dex VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # generated files 13 | bin/ 14 | gen/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Windows thumbnail db 20 | Thumbs.db 21 | 22 | # OSX files 23 | .DS_Store 24 | 25 | # Android Studio 26 | .idea/ 27 | .gradle 28 | build/ 29 | *.iml 30 | captures/ 31 | .externalNativeBuild -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | namespace "com.techyourchance.multithreading" 6 | compileSdk 34 7 | defaultConfig { 8 | applicationId "com.techyourchance.multithreading" 9 | minSdkVersion 24 10 | targetSdkVersion 34 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | 16 | signingConfigs { 17 | release { 18 | storeFile file('../release.keystore') 19 | storePassword 'release' 20 | keyAlias 'release' 21 | keyPassword 'release' 22 | } 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled true 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | signingConfig signingConfigs.release 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_17 34 | targetCompatibility JavaVersion.VERSION_17 35 | } 36 | 37 | } 38 | 39 | dependencies { 40 | implementation project(':fragmenthelper') 41 | 42 | implementation 'androidx.appcompat:appcompat:1.1.0' 43 | implementation 'com.techyourchance:threadposter:1.0.1' 44 | 45 | implementation 'io.reactivex.rxjava2:rxjava:2.2.13' 46 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 47 | 48 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 49 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2' 50 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2' 51 | } 52 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/DefaultConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading; 2 | 3 | public class DefaultConfiguration { 4 | 5 | public static final int DEFAULT_NUM_OF_MESSAGES = 1000; 6 | public static final int DEFAULT_BLOCKING_QUEUE_SIZE = 5; 7 | public static final int DEFAULT_PRODUCER_DELAY_MS = 0; 8 | 9 | public static final int DEFAULT_FACTORIAL_TIMEOUT_MS = 1000; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import android.os.Bundle; 7 | import android.view.Choreographer; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageButton; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.fragmenthelper.FragmentContainerWrapper; 14 | import com.techyourchance.multithreading.common.ToolbarManipulator; 15 | import com.techyourchance.multithreading.common.ScreensNavigator; 16 | import com.techyourchance.multithreading.common.dependencyinjection.PresentationCompositionRoot; 17 | 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Modifier; 20 | 21 | public class MainActivity extends AppCompatActivity implements 22 | FragmentContainerWrapper, 23 | ToolbarManipulator { 24 | 25 | private PresentationCompositionRoot mPresentationCompositionRoot; 26 | private ScreensNavigator mScreensNavigator; 27 | 28 | private ImageButton mBtnBack; 29 | private TextView mTxtScreenTitle; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | 36 | mPresentationCompositionRoot = new PresentationCompositionRoot( 37 | this, 38 | ((MyApplication)getApplication()).getApplicationCompositionRoot() 39 | ); 40 | 41 | mScreensNavigator = mPresentationCompositionRoot.getScreensNavigator(); 42 | 43 | mBtnBack = findViewById(R.id.btn_back); 44 | mTxtScreenTitle = findViewById(R.id.txt_screen_title); 45 | 46 | mBtnBack.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | mScreensNavigator.navigateUp(); 50 | } 51 | }); 52 | 53 | if (savedInstanceState == null) { 54 | mScreensNavigator.toHomeScreen(); 55 | } 56 | 57 | reduceChoreographerSkippedFramesWarningThreshold(); 58 | } 59 | 60 | private void reduceChoreographerSkippedFramesWarningThreshold() { 61 | Field field = null; 62 | try { 63 | field = Choreographer.class.getDeclaredField("SKIPPED_FRAME_WARNING_LIMIT" ); 64 | field.setAccessible(true); 65 | field.setInt(field, field.getModifiers() & ~Modifier.FINAL); 66 | field.set(null, 1); 67 | } catch (NoSuchFieldException|IllegalAccessException e) { 68 | // probably failed to change Choreographer's field, but it's not critical 69 | } 70 | } 71 | 72 | @Override 73 | public void onBackPressed() { 74 | mScreensNavigator.navigateBack(); 75 | } 76 | 77 | @NonNull 78 | @Override 79 | public ViewGroup getFragmentContainer() { 80 | return findViewById(R.id.frame_content); 81 | } 82 | 83 | @Override 84 | public void setScreenTitle(String screenTitle) { 85 | mTxtScreenTitle.setText(screenTitle); 86 | } 87 | 88 | @Override 89 | public void showUpButton() { 90 | mBtnBack.setVisibility(View.VISIBLE); 91 | } 92 | 93 | @Override 94 | public void hideUpButton() { 95 | mBtnBack.setVisibility(View.INVISIBLE); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading; 2 | 3 | import android.app.Application; 4 | 5 | import com.techyourchance.multithreading.common.dependencyinjection.ApplicationCompositionRoot; 6 | 7 | import static kotlinx.coroutines.DispatchersKt.IO_PARALLELISM_PROPERTY_NAME; 8 | 9 | public class MyApplication extends Application { 10 | 11 | private final ApplicationCompositionRoot mApplicationCompositionRoot = 12 | new ApplicationCompositionRoot(); 13 | 14 | public ApplicationCompositionRoot getApplicationCompositionRoot() { 15 | return mApplicationCompositionRoot; 16 | } 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | System.setProperty(IO_PARALLELISM_PROPERTY_NAME, String.valueOf(Integer.MAX_VALUE)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/common/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.common; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import com.techyourchance.fragmenthelper.HierarchicalFragment; 7 | import com.techyourchance.multithreading.MyApplication; 8 | import com.techyourchance.multithreading.common.dependencyinjection.PresentationCompositionRoot; 9 | import com.techyourchance.multithreading.home.HomeFragment; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.annotation.Nullable; 13 | import androidx.fragment.app.Fragment; 14 | 15 | public abstract class BaseFragment extends Fragment implements HierarchicalFragment { 16 | 17 | private PresentationCompositionRoot mPresentationCompositionRoot; 18 | 19 | protected final PresentationCompositionRoot getCompositionRoot() { 20 | if (mPresentationCompositionRoot == null) { 21 | mPresentationCompositionRoot = new PresentationCompositionRoot( 22 | requireActivity(), 23 | ((MyApplication)requireActivity().getApplication()).getApplicationCompositionRoot() 24 | ); 25 | } 26 | return mPresentationCompositionRoot; 27 | } 28 | 29 | @Nullable 30 | @Override 31 | public Fragment getHierarchicalParentFragment() { 32 | return HomeFragment.newInstance(); 33 | } 34 | 35 | @Override 36 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 37 | ToolbarManipulator toolbarManipulator = getCompositionRoot().getToolbarManipulator(); 38 | toolbarManipulator.setScreenTitle(getScreenTitle()); 39 | if (getHierarchicalParentFragment() != null) { 40 | toolbarManipulator.showUpButton(); 41 | } else { 42 | toolbarManipulator.hideUpButton(); 43 | } 44 | } 45 | 46 | protected abstract String getScreenTitle(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/common/BaseObservable.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.common; 2 | 3 | import java.util.Collections; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | public abstract class BaseObservable { 8 | 9 | private final Object MONITOR = new Object(); 10 | 11 | private final Set mListeners = new HashSet<>(); 12 | 13 | 14 | public void registerListener(LISTENER_CLASS listener) { 15 | synchronized (MONITOR) { 16 | boolean hadNoListeners = mListeners.size() == 0; 17 | mListeners.add(listener); 18 | if (hadNoListeners && mListeners.size() == 1) { 19 | onFirstListenerRegistered(); 20 | } 21 | } 22 | } 23 | 24 | public void unregisterListener(LISTENER_CLASS listener) { 25 | synchronized (MONITOR) { 26 | boolean hadOneListener = mListeners.size() == 1; 27 | mListeners.remove(listener); 28 | if (hadOneListener && mListeners.size() == 0) { 29 | onLastListenerUnregistered(); 30 | } 31 | } 32 | } 33 | 34 | protected Set getListeners() { 35 | synchronized (MONITOR) { 36 | return Collections.unmodifiableSet(new HashSet<>(mListeners)); 37 | } 38 | } 39 | 40 | protected void onFirstListenerRegistered() { 41 | 42 | } 43 | 44 | protected void onLastListenerUnregistered() { 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/common/ToolbarManipulator.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.common; 2 | 3 | public interface ToolbarManipulator { 4 | void setScreenTitle(String screenTitle); 5 | void showUpButton(); 6 | void hideUpButton(); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/common/dependencyinjection/ApplicationCompositionRoot.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.common.dependencyinjection; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import com.techyourchance.fragmenthelper.FragmentContainerWrapper; 8 | import com.techyourchance.fragmenthelper.FragmentHelper; 9 | import com.techyourchance.multithreading.common.ScreensNavigator; 10 | import com.techyourchance.multithreading.common.ToolbarManipulator; 11 | 12 | import java.util.concurrent.SynchronousQueue; 13 | import java.util.concurrent.ThreadFactory; 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | import androidx.fragment.app.FragmentActivity; 18 | 19 | public class ApplicationCompositionRoot { 20 | 21 | private ThreadPoolExecutor mThreadPoolExecutor; 22 | 23 | public ThreadPoolExecutor getThreadPool() { 24 | if (mThreadPoolExecutor == null) { 25 | mThreadPoolExecutor = new ThreadPoolExecutor( 26 | 10, 27 | Integer.MAX_VALUE, 28 | 10, 29 | TimeUnit.SECONDS, 30 | new SynchronousQueue<>(), 31 | new ThreadFactory() { 32 | @Override 33 | public Thread newThread(Runnable r) { 34 | Log.d("ThreadFactory", 35 | String.format("size %s, active count %s, queue remaining %s", 36 | mThreadPoolExecutor.getPoolSize(), 37 | mThreadPoolExecutor.getActiveCount(), 38 | mThreadPoolExecutor.getQueue().remainingCapacity() 39 | ) 40 | ); 41 | return new Thread(r); 42 | } 43 | } 44 | ); 45 | } 46 | return mThreadPoolExecutor; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/common/dependencyinjection/PresentationCompositionRoot.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.common.dependencyinjection; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import com.techyourchance.fragmenthelper.FragmentContainerWrapper; 8 | import com.techyourchance.fragmenthelper.FragmentHelper; 9 | import com.techyourchance.multithreading.common.ToolbarManipulator; 10 | import com.techyourchance.multithreading.common.ScreensNavigator; 11 | 12 | import java.util.concurrent.SynchronousQueue; 13 | import java.util.concurrent.ThreadFactory; 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | import androidx.fragment.app.FragmentActivity; 18 | 19 | public class PresentationCompositionRoot { 20 | 21 | private final FragmentActivity mActivity; 22 | private final ApplicationCompositionRoot mApplicationCompositionRoot; 23 | 24 | public PresentationCompositionRoot(FragmentActivity activity, ApplicationCompositionRoot applicationCompositionRoot) { 25 | mActivity = activity; 26 | mApplicationCompositionRoot = applicationCompositionRoot; 27 | } 28 | 29 | public ScreensNavigator getScreensNavigator() { 30 | return new ScreensNavigator(getFragmentHelper()); 31 | } 32 | 33 | private FragmentHelper getFragmentHelper() { 34 | return new FragmentHelper(mActivity, getFragmentContainerWrapper(), mActivity.getSupportFragmentManager()); 35 | } 36 | 37 | private FragmentContainerWrapper getFragmentContainerWrapper() { 38 | return (FragmentContainerWrapper) mActivity; 39 | } 40 | 41 | public ToolbarManipulator getToolbarManipulator() { 42 | return (ToolbarManipulator) mActivity; 43 | } 44 | 45 | public Handler getUiHandler() { 46 | return new Handler(Looper.getMainLooper()); 47 | } 48 | 49 | public ThreadPoolExecutor getThreadPool() { 50 | return mApplicationCompositionRoot.getThreadPool(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/common/math/MathUtils.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.common.math; 2 | 3 | public class MathUtils { 4 | 5 | /** 6 | * Multiply two longs. Copied from Math.multiplyExact(long, long). 7 | * @throws ArithmeticException in case of an overflow 8 | */ 9 | public static long multiplyExact(long x, long y) { 10 | long r = x * y; 11 | long ax = Math.abs(x); 12 | long ay = Math.abs(y); 13 | if (((ax | ay) >>> 31 != 0)) { 14 | // Some bits greater than 2^31 that might cause overflow 15 | // Check the result using the divide operator 16 | // and check for the special case of Long.MIN_VALUE * -1 17 | if (((y != 0) && (r / y != x)) || 18 | (x == Long.MIN_VALUE && y == -1)) { 19 | throw new ArithmeticException("long overflow"); 20 | } 21 | } 22 | return r; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/atomicity/AtomicityDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.atomicity; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Button; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.R; 14 | import com.techyourchance.multithreading.common.BaseFragment; 15 | 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | import androidx.fragment.app.Fragment; 19 | 20 | 21 | @SuppressLint("SetTextI18n") 22 | public class AtomicityDemonstrationFragment extends BaseFragment { 23 | 24 | private static final int COUNT_UP_TO = 1000; 25 | private static final int NUM_OF_COUNTER_THREADS = 100; 26 | 27 | public static Fragment newInstance() { 28 | return new AtomicityDemonstrationFragment(); 29 | } 30 | 31 | private Button mBtnStartCount; 32 | private TextView mTxtFinalCount; 33 | 34 | private Handler mUiHandler = new Handler(Looper.getMainLooper()); 35 | 36 | private volatile int mCount; 37 | 38 | @Nullable 39 | @Override 40 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 41 | View view = inflater.inflate(R.layout.fragment_atomicity_demonstration, container, false); 42 | 43 | mTxtFinalCount = view.findViewById(R.id.txt_final_count); 44 | 45 | mBtnStartCount = view.findViewById(R.id.btn_start_count); 46 | mBtnStartCount.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View v) { 49 | startCount(); 50 | } 51 | }); 52 | 53 | return view; 54 | } 55 | 56 | @Override 57 | protected String getScreenTitle() { 58 | return ""; 59 | } 60 | 61 | @Override 62 | public void onStart() { 63 | super.onStart(); 64 | } 65 | 66 | @Override 67 | public void onStop() { 68 | super.onStop(); 69 | } 70 | 71 | private void startCount() { 72 | mCount = 0; 73 | mTxtFinalCount.setText(""); 74 | mBtnStartCount.setEnabled(false); 75 | 76 | for (int i = 0; i < NUM_OF_COUNTER_THREADS; i++) { 77 | startCountThread(); 78 | } 79 | 80 | mUiHandler.postDelayed(new Runnable() { 81 | @Override 82 | public void run() { 83 | mTxtFinalCount.setText(String.valueOf(mCount)); 84 | mBtnStartCount.setEnabled(true); 85 | } 86 | }, NUM_OF_COUNTER_THREADS * 20); 87 | } 88 | 89 | private void startCountThread() { 90 | new Thread(new Runnable() { 91 | @Override 92 | public void run() { 93 | for (int i = 0; i < COUNT_UP_TO; i++) { 94 | mCount++; 95 | } 96 | } 97 | }).start(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/bestjavaimplementation/MyBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.bestjavaimplementation; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * Simplified implementation of blocking queue. 8 | */ 9 | class MyBlockingQueue { 10 | 11 | private final Object QUEUE_LOCK = new Object(); 12 | 13 | private final int mCapacity; 14 | private final Queue mQueue = new LinkedList<>(); 15 | 16 | private int mCurrentSize = 0; 17 | 18 | MyBlockingQueue(int capacity) { 19 | mCapacity = capacity; 20 | } 21 | 22 | /** 23 | * Inserts the specified element into this queue, waiting if necessary 24 | * for space to become available. 25 | * 26 | * @param number the element to add 27 | */ 28 | public void put(int number) { 29 | synchronized (QUEUE_LOCK) { 30 | while (mCurrentSize >= mCapacity) { 31 | try { 32 | QUEUE_LOCK.wait(); 33 | } catch (InterruptedException e) { 34 | return; 35 | } 36 | } 37 | 38 | mQueue.offer(number); 39 | mCurrentSize++; 40 | QUEUE_LOCK.notifyAll(); 41 | } 42 | } 43 | 44 | /** 45 | * Retrieves and removes the head of this queue, waiting if necessary 46 | * until an element becomes available. 47 | * 48 | * @return the head of this queue 49 | */ 50 | public int take() { 51 | synchronized (QUEUE_LOCK) { 52 | while (mCurrentSize <= 0) { 53 | try { 54 | QUEUE_LOCK.wait(); 55 | } catch (InterruptedException e) { 56 | return 0; 57 | } 58 | } 59 | mCurrentSize--; 60 | QUEUE_LOCK.notifyAll(); 61 | return mQueue.poll(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/bestjavaimplementation/ProducerConsumerBenchmarkUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.bestjavaimplementation; 2 | 3 | import com.techyourchance.multithreading.DefaultConfiguration; 4 | import com.techyourchance.multithreading.common.BaseObservable; 5 | import com.techyourchance.threadposter.BackgroundThreadPoster; 6 | import com.techyourchance.threadposter.UiThreadPoster; 7 | 8 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable { 9 | 10 | public static interface Listener { 11 | void onBenchmarkCompleted(Result result); 12 | } 13 | 14 | public static class Result { 15 | private final long mExecutionTime; 16 | private final int mNumOfReceivedMessages; 17 | 18 | public Result(long executionTime, int numOfReceivedMessages) { 19 | mExecutionTime = executionTime; 20 | mNumOfReceivedMessages = numOfReceivedMessages; 21 | } 22 | 23 | public long getExecutionTime() { 24 | return mExecutionTime; 25 | } 26 | 27 | public int getNumOfReceivedMessages() { 28 | return mNumOfReceivedMessages; 29 | } 30 | } 31 | 32 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES; 33 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE; 34 | 35 | private final Object LOCK = new Object(); 36 | 37 | private final UiThreadPoster mUiThreadPoster = new UiThreadPoster(); 38 | private final BackgroundThreadPoster mBackgroundThreadPoster = new BackgroundThreadPoster(); 39 | 40 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY); 41 | 42 | private int mNumOfFinishedConsumers; 43 | 44 | private int mNumOfReceivedMessages; 45 | 46 | public void startBenchmarkAndNotify() { 47 | mBackgroundThreadPoster.post(() -> { 48 | 49 | mNumOfReceivedMessages = 0; 50 | mNumOfFinishedConsumers = 0; 51 | long startTimestamp = System.currentTimeMillis(); 52 | 53 | // producers init thread 54 | mBackgroundThreadPoster.post(() -> { 55 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 56 | startNewProducer(i); 57 | } 58 | }); 59 | 60 | // consumers init thread 61 | mBackgroundThreadPoster.post(() -> { 62 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 63 | startNewConsumer(); 64 | } 65 | }); 66 | 67 | waitForAllConsumersToFinish(); 68 | 69 | Result result; 70 | synchronized (LOCK) { 71 | result = new Result( 72 | System.currentTimeMillis() - startTimestamp, 73 | mNumOfReceivedMessages 74 | ); 75 | } 76 | 77 | notifySuccess(result); 78 | 79 | }); 80 | 81 | } 82 | 83 | private void waitForAllConsumersToFinish() { 84 | synchronized (LOCK) { 85 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) { 86 | try { 87 | LOCK.wait(); 88 | } catch (InterruptedException e) { 89 | return; 90 | } 91 | } 92 | } 93 | } 94 | 95 | private void startNewProducer(final int index) { 96 | mBackgroundThreadPoster.post(() -> { 97 | try { 98 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS); 99 | } catch (InterruptedException e) { 100 | return; 101 | } 102 | mBlockingQueue.put(index); 103 | }); 104 | } 105 | 106 | private void startNewConsumer() { 107 | mBackgroundThreadPoster.post(() -> { 108 | int message = mBlockingQueue.take(); 109 | synchronized (LOCK) { 110 | if (message != -1) { 111 | mNumOfReceivedMessages++; 112 | } 113 | mNumOfFinishedConsumers++; 114 | LOCK.notifyAll(); 115 | } 116 | }); 117 | } 118 | 119 | private void notifySuccess(Result result) { 120 | mUiThreadPoster.post(() -> { 121 | for (Listener listener : getListeners()) { 122 | listener.onBenchmarkCompleted(result); 123 | } 124 | }); 125 | } 126 | 127 | 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/customhandler/CustomHandlerDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.customhandler; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | 11 | import com.techyourchance.multithreading.R; 12 | import com.techyourchance.multithreading.common.BaseFragment; 13 | 14 | import java.util.concurrent.BlockingQueue; 15 | import java.util.concurrent.LinkedBlockingQueue; 16 | 17 | import androidx.annotation.NonNull; 18 | import androidx.annotation.Nullable; 19 | import androidx.fragment.app.Fragment; 20 | 21 | 22 | @SuppressLint("SetTextI18n") 23 | public class CustomHandlerDemonstrationFragment extends BaseFragment { 24 | 25 | private static final int SECONDS_TO_COUNT = 5; 26 | 27 | public static Fragment newInstance() { 28 | return new CustomHandlerDemonstrationFragment(); 29 | } 30 | 31 | private Button mBtnSendJob; 32 | 33 | private CustomHandler mCustomHandler; 34 | 35 | @Nullable 36 | @Override 37 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 38 | View view = inflater.inflate(R.layout.fragment_custom_looper_demonstration, container, false); 39 | 40 | mBtnSendJob = view.findViewById(R.id.btn_send_job); 41 | mBtnSendJob.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | sendJob(); 45 | } 46 | }); 47 | 48 | return view; 49 | } 50 | 51 | @Override 52 | protected String getScreenTitle() { 53 | return ""; 54 | } 55 | 56 | @Override 57 | public void onStart() { 58 | super.onStart(); 59 | mCustomHandler = new CustomHandler(); 60 | } 61 | 62 | @Override 63 | public void onStop() { 64 | super.onStop(); 65 | mCustomHandler.stop(); 66 | } 67 | 68 | private void sendJob() { 69 | mCustomHandler.post(new Runnable() { 70 | @Override 71 | public void run() { 72 | for (int i=0; i < SECONDS_TO_COUNT; i++) { 73 | try { 74 | Thread.sleep(1000); 75 | } catch (InterruptedException e) { 76 | return; 77 | } 78 | Log.d("CustomHandler", "iteration: " + i); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | private class CustomHandler { 85 | 86 | private final Runnable POISON = new Runnable() { 87 | @Override 88 | public void run() {} 89 | }; 90 | 91 | private final BlockingQueue mQueue = new LinkedBlockingQueue<>(); 92 | 93 | public CustomHandler() { 94 | initWorkerThread(); 95 | } 96 | 97 | private void initWorkerThread() { 98 | new Thread(new Runnable() { 99 | @Override 100 | public void run() { 101 | Log.d("CustomHandler", "worker (looper) thread initialized"); 102 | while (true) { 103 | Runnable runnable; 104 | try { 105 | runnable = mQueue.take(); 106 | } catch (InterruptedException e) { 107 | return; 108 | } 109 | if (runnable == POISON) { 110 | Log.d("CustomHandler", "poison data detected; stopping working thread"); 111 | return; 112 | } 113 | runnable.run(); 114 | } 115 | } 116 | }).start(); 117 | } 118 | 119 | public void stop() { 120 | Log.d("CustomHandler", "injecting poison data into the queue"); 121 | mQueue.clear(); 122 | mQueue.add(POISON); 123 | } 124 | 125 | public void post(Runnable job) { 126 | mQueue.add(job); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designasynctask/DesignWithAsyncTaskDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designasynctask; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.techyourchance.multithreading.R; 12 | import com.techyourchance.multithreading.common.BaseFragment; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class DesignWithAsyncTaskDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener { 19 | 20 | public static Fragment newInstance() { 21 | return new DesignWithAsyncTaskDemonstrationFragment(); 22 | } 23 | 24 | private Button mBtnStart; 25 | private ProgressBar mProgressBar; 26 | private TextView mTxtReceivedMessagesCount; 27 | private TextView mTxtExecutionTime; 28 | 29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase; 30 | 31 | @Override 32 | public void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase(); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 40 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false); 41 | 42 | mBtnStart = view.findViewById(R.id.btn_start); 43 | mProgressBar = view.findViewById(R.id.progress); 44 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count); 45 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time); 46 | 47 | mBtnStart.setOnClickListener(v -> { 48 | mBtnStart.setEnabled(false); 49 | mTxtReceivedMessagesCount.setText(""); 50 | mTxtExecutionTime.setText(""); 51 | mProgressBar.setVisibility(View.VISIBLE); 52 | 53 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify(); 54 | }); 55 | 56 | return view; 57 | } 58 | 59 | @Override 60 | protected String getScreenTitle() { 61 | return ""; 62 | } 63 | 64 | @Override 65 | public void onStart() { 66 | super.onStart(); 67 | mProducerConsumerBenchmarkUseCase.registerListener(this); 68 | } 69 | 70 | @Override 71 | public void onStop() { 72 | super.onStop(); 73 | mProducerConsumerBenchmarkUseCase.unregisterListener(this); 74 | } 75 | 76 | @Override 77 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) { 78 | mProgressBar.setVisibility(View.INVISIBLE); 79 | mBtnStart.setEnabled(true); 80 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages()); 81 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designasynctask/MyBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designasynctask; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * Simplified implementation of blocking queue. 8 | */ 9 | class MyBlockingQueue { 10 | 11 | private final Object QUEUE_LOCK = new Object(); 12 | 13 | private final int mCapacity; 14 | private final Queue mQueue = new LinkedList<>(); 15 | 16 | private int mCurrentSize = 0; 17 | 18 | MyBlockingQueue(int capacity) { 19 | mCapacity = capacity; 20 | } 21 | 22 | /** 23 | * Inserts the specified element into this queue, waiting if necessary 24 | * for space to become available. 25 | * 26 | * @param number the element to add 27 | */ 28 | public void put(int number) { 29 | synchronized (QUEUE_LOCK) { 30 | while (mCurrentSize >= mCapacity) { 31 | try { 32 | QUEUE_LOCK.wait(); 33 | } catch (InterruptedException e) { 34 | return; 35 | } 36 | } 37 | 38 | mQueue.offer(number); 39 | mCurrentSize++; 40 | QUEUE_LOCK.notifyAll(); 41 | } 42 | } 43 | 44 | /** 45 | * Retrieves and removes the head of this queue, waiting if necessary 46 | * until an element becomes available. 47 | * 48 | * @return the head of this queue 49 | */ 50 | public int take() { 51 | synchronized (QUEUE_LOCK) { 52 | while (mCurrentSize <= 0) { 53 | try { 54 | QUEUE_LOCK.wait(); 55 | } catch (InterruptedException e) { 56 | return 0; 57 | } 58 | } 59 | mCurrentSize--; 60 | QUEUE_LOCK.notifyAll(); 61 | return mQueue.poll(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designasynctask/ProducerConsumerBenchmarkUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designasynctask; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | 7 | import com.techyourchance.multithreading.DefaultConfiguration; 8 | import com.techyourchance.multithreading.common.BaseObservable; 9 | 10 | import androidx.annotation.UiThread; 11 | 12 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable { 13 | 14 | public static interface Listener { 15 | void onBenchmarkCompleted(Result result); 16 | } 17 | 18 | public static class Result { 19 | private final long mExecutionTime; 20 | private final int mNumOfReceivedMessages; 21 | 22 | public Result(long executionTime, int numOfReceivedMessages) { 23 | mExecutionTime = executionTime; 24 | mNumOfReceivedMessages = numOfReceivedMessages; 25 | } 26 | 27 | public long getExecutionTime() { 28 | return mExecutionTime; 29 | } 30 | 31 | public int getNumOfReceivedMessages() { 32 | return mNumOfReceivedMessages; 33 | } 34 | } 35 | 36 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES; 37 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE; 38 | 39 | private final Object LOCK = new Object(); 40 | 41 | private final Handler mUiHandler = new Handler(Looper.getMainLooper()); 42 | 43 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY); 44 | 45 | private int mNumOfFinishedConsumers; 46 | 47 | private int mNumOfReceivedMessages; 48 | 49 | private long mStartTimestamp; 50 | 51 | 52 | public void startBenchmarkAndNotify() { 53 | 54 | synchronized (LOCK) { 55 | mNumOfReceivedMessages = 0; 56 | mNumOfFinishedConsumers = 0; 57 | mStartTimestamp = System.currentTimeMillis(); 58 | } 59 | 60 | // watcher-reporter thread 61 | new AsyncTask() { 62 | 63 | @Override 64 | protected void onPreExecute() { 65 | super.onPreExecute(); 66 | } 67 | 68 | @Override 69 | protected Void doInBackground(Void... voids) { 70 | synchronized (LOCK) { 71 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) { 72 | try { 73 | LOCK.wait(); 74 | } catch (InterruptedException e) { 75 | return null; 76 | } 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | @Override 83 | protected void onPostExecute(Void aVoid) { 84 | notifySuccess(); 85 | } 86 | }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 87 | 88 | // producers init thread 89 | new Thread(() -> { 90 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 91 | startNewProducer(i); 92 | } 93 | }).start(); 94 | 95 | // consumers init thread 96 | new Thread(() -> { 97 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 98 | startNewConsumer(); 99 | } 100 | }).start(); 101 | } 102 | 103 | 104 | private void startNewProducer(final int index) { 105 | new Thread(() -> { 106 | try { 107 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS); 108 | } catch (InterruptedException e) { 109 | return; 110 | } 111 | mBlockingQueue.put(index); 112 | }).start(); 113 | } 114 | 115 | private void startNewConsumer() { 116 | new Thread(() -> { 117 | int message = mBlockingQueue.take(); 118 | synchronized (LOCK) { 119 | if (message != -1) { 120 | mNumOfReceivedMessages++; 121 | } 122 | mNumOfFinishedConsumers++; 123 | LOCK.notifyAll(); 124 | } 125 | }).start(); 126 | } 127 | 128 | @UiThread 129 | private void notifySuccess() { 130 | Result result; 131 | synchronized (LOCK) { 132 | result = 133 | new Result( 134 | System.currentTimeMillis() - mStartTimestamp, 135 | mNumOfReceivedMessages 136 | ); 137 | } 138 | for (Listener listener : getListeners()) { 139 | listener.onBenchmarkCompleted(result); 140 | } 141 | } 142 | 143 | 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designcoroutines/DesignWithCoroutinesDemonstrationFragment.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designcoroutines 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.util.Log 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.View.* 10 | import android.view.ViewGroup 11 | import android.widget.Button 12 | import android.widget.ProgressBar 13 | import android.widget.TextView 14 | import androidx.fragment.app.Fragment 15 | import com.techyourchance.multithreading.R 16 | import com.techyourchance.multithreading.common.BaseFragment 17 | import kotlinx.coroutines.CoroutineScope 18 | import kotlinx.coroutines.Dispatchers 19 | import kotlinx.coroutines.Job 20 | import kotlinx.coroutines.launch 21 | 22 | class DesignWithCoroutinesDemonstrationFragment : BaseFragment() { 23 | 24 | private lateinit var btnStart: Button 25 | private lateinit var progressBar: ProgressBar 26 | private lateinit var txtReceivedMessagesCount: TextView 27 | private lateinit var txtExecutionTime: TextView 28 | private lateinit var viewUiNonBlockedIndicator : View 29 | 30 | private lateinit var producerConsumerBenchmarkUseCase: ProducerConsumerBenchmarkUseCase 31 | 32 | private var showUiNonBlockedIndication : Boolean = false 33 | 34 | private var job : Job? = null 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | producerConsumerBenchmarkUseCase = ProducerConsumerBenchmarkUseCase() 39 | } 40 | 41 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 42 | val view = inflater.inflate(R.layout.fragment_design_with_coroutines_demonstration, container, false) 43 | 44 | view.apply { 45 | btnStart = findViewById(R.id.btn_start) 46 | progressBar = findViewById(R.id.progress) 47 | txtReceivedMessagesCount = findViewById(R.id.txt_received_messages_count) 48 | txtExecutionTime = findViewById(R.id.txt_execution_time) 49 | viewUiNonBlockedIndicator = findViewById(R.id.view_ui_non_blocked_indicator) 50 | } 51 | 52 | btnStart.setOnClickListener { _ -> 53 | btnStart.isEnabled = false 54 | txtReceivedMessagesCount.text = "" 55 | txtExecutionTime.text = "" 56 | progressBar.visibility = VISIBLE 57 | 58 | job = CoroutineScope(Dispatchers.Main).launch { 59 | val result = producerConsumerBenchmarkUseCase.startBenchmark() 60 | onBenchmarkCompleted(result) 61 | } 62 | } 63 | 64 | return view 65 | } 66 | 67 | override fun getScreenTitle(): String { 68 | return "" 69 | } 70 | 71 | override fun onStart() { 72 | super.onStart() 73 | showUiNonBlockedIndication = true 74 | postUiNonBlockedIndication() 75 | } 76 | override fun onStop() { 77 | Log.d("FragmentCoroutinesDemo", "onStop() called") 78 | super.onStop() 79 | showUiNonBlockedIndication = false 80 | job?.apply { cancel() } 81 | } 82 | 83 | private fun postUiNonBlockedIndication() { 84 | Handler(Looper.getMainLooper()).postDelayed( 85 | { 86 | if (showUiNonBlockedIndication) { 87 | val indicatorVisible = viewUiNonBlockedIndicator.visibility == VISIBLE 88 | viewUiNonBlockedIndicator.visibility = if (indicatorVisible) INVISIBLE else VISIBLE 89 | postUiNonBlockedIndication() 90 | } 91 | }, 92 | 500 93 | ) 94 | } 95 | 96 | 97 | fun onBenchmarkCompleted(result: ProducerConsumerBenchmarkUseCase.Result) { 98 | Log.d("FragmentCoroutinesDemo", "onBenchmarkCompleted() called") 99 | progressBar.visibility = INVISIBLE 100 | btnStart.isEnabled = true 101 | txtReceivedMessagesCount.text = "Received messages: ${result.numOfReceivedMessages}" 102 | txtExecutionTime.text = "Execution time: ${result.executionTime} ms" 103 | } 104 | 105 | companion object { 106 | fun newInstance(): Fragment { 107 | return DesignWithCoroutinesDemonstrationFragment() 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designcoroutines/MyBlockingQueue.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designcoroutines 2 | 3 | import java.util.LinkedList 4 | import java.util.concurrent.locks.ReentrantLock 5 | import kotlin.concurrent.withLock 6 | 7 | /** 8 | * Simplified implementation of blocking queue. 9 | */ 10 | internal class MyBlockingQueue(private val capacity: Int) { 11 | 12 | private val reentrantLock = ReentrantLock() 13 | private val lockCondition = reentrantLock.newCondition() 14 | 15 | private val queue = LinkedList() 16 | 17 | private var currentSize = 0 18 | 19 | /** 20 | * Inserts the specified element into this queue, waiting if necessary 21 | * for space to become available. 22 | * 23 | * @param number the element to add 24 | */ 25 | fun put(number: Int) { 26 | reentrantLock.withLock { 27 | while (currentSize >= capacity) { 28 | try { 29 | lockCondition.await() 30 | } catch (e: InterruptedException) { 31 | return 32 | } 33 | } 34 | queue.offer(number) 35 | currentSize++ 36 | lockCondition.signalAll() 37 | } 38 | } 39 | 40 | /** 41 | * Retrieves and removes the head of this queue, waiting if necessary 42 | * until an element becomes available. 43 | * 44 | * @return the head of this queue 45 | */ 46 | fun take(): Int { 47 | reentrantLock.withLock { 48 | while (currentSize <= 0) { 49 | try { 50 | lockCondition.await() 51 | } catch (e: InterruptedException) { 52 | return 0 53 | } 54 | } 55 | currentSize-- 56 | lockCondition.signalAll() 57 | return queue.poll() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designcoroutines/ProducerConsumerBenchmarkUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designcoroutines 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import android.util.Log 6 | import com.techyourchance.multithreading.DefaultConfiguration 7 | import kotlinx.coroutines.* 8 | import java.util.concurrent.atomic.AtomicInteger 9 | 10 | class ProducerConsumerBenchmarkUseCase { 11 | 12 | class Result(val executionTime: Long, val numOfReceivedMessages: Int) 13 | 14 | private val blockingQueue = MyBlockingQueue(BLOCKING_QUEUE_CAPACITY) 15 | 16 | private val numOfReceivedMessages: AtomicInteger = AtomicInteger(0) 17 | private val numOfProducers: AtomicInteger = AtomicInteger(0) 18 | private val numOfConsumers: AtomicInteger = AtomicInteger(0) 19 | 20 | suspend fun startBenchmark() : Result { 21 | 22 | return withContext(Dispatchers.IO) { 23 | 24 | numOfReceivedMessages.set(0) 25 | numOfProducers.set(0) 26 | numOfConsumers.set(0) 27 | 28 | val startTimestamp = System.currentTimeMillis() 29 | 30 | // producers init coroutine 31 | val deferredProducers = async(Dispatchers.IO + NonCancellable) { 32 | for (i in 0 until NUM_OF_MESSAGES) { 33 | startNewProducer(i) 34 | } 35 | } 36 | 37 | // consumers init coroutine 38 | val deferredConsumers = async(Dispatchers.IO + NonCancellable) { 39 | for (i in 0 until NUM_OF_MESSAGES) { 40 | startNewConsumer() 41 | } 42 | } 43 | 44 | awaitAll(deferredConsumers, deferredProducers) 45 | 46 | Result( 47 | System.currentTimeMillis() - startTimestamp, 48 | numOfReceivedMessages.get() 49 | ) 50 | } 51 | 52 | } 53 | 54 | private fun CoroutineScope.startNewProducer(index: Int) = launch(Dispatchers.IO) { 55 | Log.d("Producer", "producer ${numOfProducers.incrementAndGet()} started; " + 56 | "on thread ${Thread.currentThread().name}"); 57 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS.toLong()) 58 | blockingQueue.put(index) 59 | } 60 | 61 | private fun CoroutineScope.startNewConsumer() = launch(Dispatchers.IO) { 62 | Log.d("Consumer", "consumer ${numOfConsumers.incrementAndGet()} started; " + 63 | "on thread ${Thread.currentThread().name}"); 64 | val message = blockingQueue.take() 65 | if (message != -1) { 66 | numOfReceivedMessages.incrementAndGet() 67 | } 68 | } 69 | 70 | companion object { 71 | private const val NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES 72 | private const val BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designrxjava/DesignWithRxJavaDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designrxjava; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.techyourchance.multithreading.R; 12 | import com.techyourchance.multithreading.common.BaseFragment; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.fragment.app.Fragment; 17 | import io.reactivex.android.schedulers.AndroidSchedulers; 18 | import io.reactivex.disposables.Disposable; 19 | import io.reactivex.schedulers.Schedulers; 20 | 21 | public class DesignWithRxJavaDemonstrationFragment extends BaseFragment { 22 | 23 | public static Fragment newInstance() { 24 | return new DesignWithRxJavaDemonstrationFragment(); 25 | } 26 | 27 | private Button mBtnStart; 28 | private ProgressBar mProgressBar; 29 | private TextView mTxtReceivedMessagesCount; 30 | private TextView mTxtExecutionTime; 31 | 32 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase; 33 | 34 | private @Nullable Disposable mDisposable; 35 | 36 | @Override 37 | public void onCreate(@Nullable Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase(); 40 | } 41 | 42 | @Nullable 43 | @Override 44 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 45 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false); 46 | 47 | mBtnStart = view.findViewById(R.id.btn_start); 48 | mProgressBar = view.findViewById(R.id.progress); 49 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count); 50 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time); 51 | 52 | mBtnStart.setOnClickListener(v -> { 53 | mBtnStart.setEnabled(false); 54 | mTxtReceivedMessagesCount.setText(""); 55 | mTxtExecutionTime.setText(""); 56 | mProgressBar.setVisibility(View.VISIBLE); 57 | 58 | mDisposable = mProducerConsumerBenchmarkUseCase.startBenchmark() 59 | .subscribeOn(Schedulers.io()) 60 | .observeOn(AndroidSchedulers.mainThread()) 61 | .subscribe(this::onBenchmarkCompleted); 62 | }); 63 | 64 | return view; 65 | } 66 | 67 | @Override 68 | protected String getScreenTitle() { 69 | return ""; 70 | } 71 | 72 | @Override 73 | public void onStop() { 74 | super.onStop(); 75 | if (mDisposable != null) { 76 | mDisposable.dispose(); 77 | } 78 | } 79 | 80 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) { 81 | mProgressBar.setVisibility(View.INVISIBLE); 82 | mBtnStart.setEnabled(true); 83 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages()); 84 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designrxjava/MyBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designrxjava; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * Simplified implementation of blocking queue. 8 | */ 9 | class MyBlockingQueue { 10 | 11 | private final Object QUEUE_LOCK = new Object(); 12 | 13 | private final int mCapacity; 14 | private final Queue mQueue = new LinkedList<>(); 15 | 16 | private int mCurrentSize = 0; 17 | 18 | MyBlockingQueue(int capacity) { 19 | mCapacity = capacity; 20 | } 21 | 22 | /** 23 | * Inserts the specified element into this queue, waiting if necessary 24 | * for space to become available. 25 | * 26 | * @param number the element to add 27 | */ 28 | public void put(int number) { 29 | synchronized (QUEUE_LOCK) { 30 | while (mCurrentSize >= mCapacity) { 31 | try { 32 | QUEUE_LOCK.wait(); 33 | } catch (InterruptedException e) { 34 | return; 35 | } 36 | } 37 | 38 | mQueue.offer(number); 39 | mCurrentSize++; 40 | QUEUE_LOCK.notifyAll(); 41 | } 42 | } 43 | 44 | /** 45 | * Retrieves and removes the head of this queue, waiting if necessary 46 | * until an element becomes available. 47 | * 48 | * @return the head of this queue 49 | */ 50 | public int take() { 51 | synchronized (QUEUE_LOCK) { 52 | while (mCurrentSize <= 0) { 53 | try { 54 | QUEUE_LOCK.wait(); 55 | } catch (InterruptedException e) { 56 | return 0; 57 | } 58 | } 59 | mCurrentSize--; 60 | QUEUE_LOCK.notifyAll(); 61 | return mQueue.poll(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designrxjava/ProducerConsumerBenchmarkUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designrxjava; 2 | 3 | import android.util.Log; 4 | 5 | import com.techyourchance.multithreading.DefaultConfiguration; 6 | 7 | import java.util.concurrent.Callable; 8 | 9 | import io.reactivex.Flowable; 10 | import io.reactivex.Observable; 11 | import io.reactivex.schedulers.Schedulers; 12 | 13 | public class ProducerConsumerBenchmarkUseCase { 14 | 15 | public static class Result { 16 | private final long mExecutionTime; 17 | private final int mNumOfReceivedMessages; 18 | 19 | public Result(long executionTime, int numOfReceivedMessages) { 20 | mExecutionTime = executionTime; 21 | mNumOfReceivedMessages = numOfReceivedMessages; 22 | } 23 | 24 | public long getExecutionTime() { 25 | return mExecutionTime; 26 | } 27 | 28 | public int getNumOfReceivedMessages() { 29 | return mNumOfReceivedMessages; 30 | } 31 | } 32 | 33 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES; 34 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE; 35 | 36 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY); 37 | 38 | private long mStartTimestamp; 39 | 40 | 41 | public Observable startBenchmark() { 42 | return Flowable.range(0, NUM_OF_MESSAGES) 43 | .flatMap(id -> Flowable 44 | .fromCallable(() -> { 45 | try { 46 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS); 47 | } catch (InterruptedException e) { 48 | return id; 49 | } 50 | mBlockingQueue.put(id); 51 | return id; 52 | }) // <-- generate message 53 | .subscribeOn(Schedulers.io()) 54 | ) 55 | .parallel(NUM_OF_MESSAGES) 56 | .runOn(Schedulers.io()) 57 | .doOnNext(msg -> { mBlockingQueue.take(); }) // <-- process message 58 | .sequential() 59 | .count() 60 | .doOnSubscribe(s -> { mStartTimestamp = System.currentTimeMillis(); }) 61 | .map(cnt -> new Result(System.currentTimeMillis() - mStartTimestamp, cnt.intValue())) 62 | .toObservable(); 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthread/DesignWithThreadsDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthread; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.techyourchance.multithreading.R; 12 | import com.techyourchance.multithreading.common.BaseFragment; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class DesignWithThreadsDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener { 19 | 20 | public static Fragment newInstance() { 21 | return new DesignWithThreadsDemonstrationFragment(); 22 | } 23 | 24 | private Button mBtnStart; 25 | private ProgressBar mProgressBar; 26 | private TextView mTxtReceivedMessagesCount; 27 | private TextView mTxtExecutionTime; 28 | 29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase; 30 | 31 | @Override 32 | public void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase(); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 40 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false); 41 | 42 | mBtnStart = view.findViewById(R.id.btn_start); 43 | mProgressBar = view.findViewById(R.id.progress); 44 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count); 45 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time); 46 | 47 | mBtnStart.setOnClickListener(v -> { 48 | mBtnStart.setEnabled(false); 49 | mTxtReceivedMessagesCount.setText(""); 50 | mTxtExecutionTime.setText(""); 51 | mProgressBar.setVisibility(View.VISIBLE); 52 | 53 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify(); 54 | }); 55 | 56 | return view; 57 | } 58 | 59 | @Override 60 | protected String getScreenTitle() { 61 | return ""; 62 | } 63 | 64 | @Override 65 | public void onStart() { 66 | super.onStart(); 67 | mProducerConsumerBenchmarkUseCase.registerListener(this); 68 | } 69 | 70 | @Override 71 | public void onStop() { 72 | super.onStop(); 73 | mProducerConsumerBenchmarkUseCase.unregisterListener(this); 74 | } 75 | 76 | @Override 77 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) { 78 | mProgressBar.setVisibility(View.INVISIBLE); 79 | mBtnStart.setEnabled(true); 80 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages()); 81 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthread/MyBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthread; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * Simplified implementation of blocking queue. 8 | */ 9 | class MyBlockingQueue { 10 | 11 | private final Object QUEUE_LOCK = new Object(); 12 | 13 | private final int mCapacity; 14 | private final Queue mQueue = new LinkedList<>(); 15 | 16 | private int mCurrentSize = 0; 17 | 18 | MyBlockingQueue(int capacity) { 19 | mCapacity = capacity; 20 | } 21 | 22 | /** 23 | * Inserts the specified element into this queue, waiting if necessary 24 | * for space to become available. 25 | * 26 | * @param number the element to add 27 | */ 28 | public void put(int number) { 29 | synchronized (QUEUE_LOCK) { 30 | while (mCurrentSize >= mCapacity) { 31 | try { 32 | QUEUE_LOCK.wait(); 33 | } catch (InterruptedException e) { 34 | return; 35 | } 36 | } 37 | 38 | mQueue.offer(number); 39 | mCurrentSize++; 40 | QUEUE_LOCK.notifyAll(); 41 | } 42 | } 43 | 44 | /** 45 | * Retrieves and removes the head of this queue, waiting if necessary 46 | * until an element becomes available. 47 | * 48 | * @return the head of this queue 49 | */ 50 | public int take() { 51 | synchronized (QUEUE_LOCK) { 52 | while (mCurrentSize <= 0) { 53 | try { 54 | QUEUE_LOCK.wait(); 55 | } catch (InterruptedException e) { 56 | return 0; 57 | } 58 | } 59 | mCurrentSize--; 60 | QUEUE_LOCK.notifyAll(); 61 | return mQueue.poll(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthread/ProducerConsumerBenchmarkUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthread; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import com.techyourchance.multithreading.DefaultConfiguration; 7 | import com.techyourchance.multithreading.common.BaseObservable; 8 | 9 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable { 10 | 11 | public static interface Listener { 12 | void onBenchmarkCompleted(Result result); 13 | } 14 | 15 | public static class Result { 16 | private final long mExecutionTime; 17 | private final int mNumOfReceivedMessages; 18 | 19 | public Result(long executionTime, int numOfReceivedMessages) { 20 | mExecutionTime = executionTime; 21 | mNumOfReceivedMessages = numOfReceivedMessages; 22 | } 23 | 24 | public long getExecutionTime() { 25 | return mExecutionTime; 26 | } 27 | 28 | public int getNumOfReceivedMessages() { 29 | return mNumOfReceivedMessages; 30 | } 31 | } 32 | 33 | 34 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES; 35 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE; 36 | 37 | private final Object LOCK = new Object(); 38 | 39 | private final Handler mUiHandler = new Handler(Looper.getMainLooper()); 40 | 41 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY); 42 | 43 | private int mNumOfFinishedConsumers; 44 | 45 | private int mNumOfReceivedMessages; 46 | 47 | private long mStartTimestamp; 48 | 49 | 50 | public void startBenchmarkAndNotify() { 51 | 52 | synchronized (LOCK) { 53 | mNumOfReceivedMessages = 0; 54 | mNumOfFinishedConsumers = 0; 55 | mStartTimestamp = System.currentTimeMillis(); 56 | } 57 | 58 | // watcher-reporter thread 59 | new Thread(() -> { 60 | synchronized (LOCK) { 61 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) { 62 | try { 63 | LOCK.wait(); 64 | } catch (InterruptedException e) { 65 | return; 66 | } 67 | } 68 | } 69 | notifySuccess(); 70 | }).start(); 71 | 72 | // producers init thread 73 | new Thread(() -> { 74 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 75 | startNewProducer(i); 76 | } 77 | }).start(); 78 | 79 | // consumers init thread 80 | new Thread(() -> { 81 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 82 | startNewConsumer(); 83 | } 84 | }).start(); 85 | } 86 | 87 | 88 | private void startNewProducer(final int index) { 89 | new Thread(() -> { 90 | try { 91 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS); 92 | } catch (InterruptedException e) { 93 | return; 94 | } 95 | mBlockingQueue.put(index); 96 | }).start(); 97 | } 98 | 99 | private void startNewConsumer() { 100 | new Thread(() -> { 101 | int message = mBlockingQueue.take(); 102 | synchronized (LOCK) { 103 | if (message != -1) { 104 | mNumOfReceivedMessages++; 105 | } 106 | mNumOfFinishedConsumers++; 107 | LOCK.notifyAll(); 108 | } 109 | }).start(); 110 | } 111 | 112 | private void notifySuccess() { 113 | mUiHandler.post(() -> { 114 | Result result; 115 | synchronized (LOCK) { 116 | result = 117 | new Result( 118 | System.currentTimeMillis() - mStartTimestamp, 119 | mNumOfReceivedMessages 120 | ); 121 | } 122 | for (Listener listener : getListeners()) { 123 | listener.onBenchmarkCompleted(result); 124 | } 125 | }); 126 | } 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadpool/DesignWithThreadPoolDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthreadpool; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.techyourchance.multithreading.R; 12 | import com.techyourchance.multithreading.common.BaseFragment; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class DesignWithThreadPoolDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener { 19 | 20 | public static Fragment newInstance() { 21 | return new DesignWithThreadPoolDemonstrationFragment(); 22 | } 23 | 24 | private Button mBtnStart; 25 | private ProgressBar mProgressBar; 26 | private TextView mTxtReceivedMessagesCount; 27 | private TextView mTxtExecutionTime; 28 | 29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase; 30 | 31 | @Override 32 | public void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase( 35 | getCompositionRoot().getUiHandler(), 36 | getCompositionRoot().getThreadPool() 37 | ); 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 43 | View view = inflater.inflate(R.layout.fragment_design_with_thread_pool_demonstration, container, false); 44 | 45 | mBtnStart = view.findViewById(R.id.btn_start); 46 | mProgressBar = view.findViewById(R.id.progress); 47 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count); 48 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time); 49 | 50 | mBtnStart.setOnClickListener(v -> { 51 | mBtnStart.setEnabled(false); 52 | mTxtReceivedMessagesCount.setText(""); 53 | mTxtExecutionTime.setText(""); 54 | mProgressBar.setVisibility(View.VISIBLE); 55 | 56 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify(); 57 | }); 58 | 59 | return view; 60 | } 61 | 62 | @Override 63 | protected String getScreenTitle() { 64 | return ""; 65 | } 66 | 67 | @Override 68 | public void onStart() { 69 | super.onStart(); 70 | mProducerConsumerBenchmarkUseCase.registerListener(this); 71 | } 72 | 73 | @Override 74 | public void onStop() { 75 | super.onStop(); 76 | mProducerConsumerBenchmarkUseCase.unregisterListener(this); 77 | } 78 | 79 | @Override 80 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) { 81 | mProgressBar.setVisibility(View.INVISIBLE); 82 | mBtnStart.setEnabled(true); 83 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages()); 84 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms"); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadpool/MyBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthreadpool; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * Simplified implementation of blocking queue. 8 | */ 9 | class MyBlockingQueue { 10 | 11 | private final Object QUEUE_LOCK = new Object(); 12 | 13 | private final int mCapacity; 14 | private final Queue mQueue = new LinkedList<>(); 15 | 16 | private int mCurrentSize = 0; 17 | 18 | MyBlockingQueue(int capacity) { 19 | mCapacity = capacity; 20 | } 21 | 22 | /** 23 | * Inserts the specified element into this queue, waiting if necessary 24 | * for space to become available. 25 | * 26 | * @param number the element to add 27 | */ 28 | public void put(int number) { 29 | synchronized (QUEUE_LOCK) { 30 | while (mCurrentSize >= mCapacity) { 31 | try { 32 | QUEUE_LOCK.wait(); 33 | } catch (InterruptedException e) { 34 | return; 35 | } 36 | } 37 | 38 | mQueue.offer(number); 39 | mCurrentSize++; 40 | QUEUE_LOCK.notifyAll(); 41 | } 42 | } 43 | 44 | /** 45 | * Retrieves and removes the head of this queue, waiting if necessary 46 | * until an element becomes available. 47 | * 48 | * @return the head of this queue 49 | */ 50 | public int take() { 51 | synchronized (QUEUE_LOCK) { 52 | while (mCurrentSize <= 0) { 53 | try { 54 | QUEUE_LOCK.wait(); 55 | } catch (InterruptedException e) { 56 | return 0; 57 | } 58 | } 59 | mCurrentSize--; 60 | QUEUE_LOCK.notifyAll(); 61 | return mQueue.poll(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadpool/ProducerConsumerBenchmarkUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthreadpool; 2 | 3 | import android.os.Handler; 4 | 5 | import com.techyourchance.multithreading.DefaultConfiguration; 6 | import com.techyourchance.multithreading.common.BaseObservable; 7 | 8 | import java.util.concurrent.ThreadPoolExecutor; 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable { 12 | 13 | public static interface Listener { 14 | void onBenchmarkCompleted(Result result); 15 | } 16 | 17 | public static class Result { 18 | private final long mExecutionTime; 19 | private final int mNumOfReceivedMessages; 20 | 21 | public Result(long executionTime, int numOfReceivedMessages) { 22 | mExecutionTime = executionTime; 23 | mNumOfReceivedMessages = numOfReceivedMessages; 24 | } 25 | 26 | public long getExecutionTime() { 27 | return mExecutionTime; 28 | } 29 | 30 | public int getNumOfReceivedMessages() { 31 | return mNumOfReceivedMessages; 32 | } 33 | } 34 | 35 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES; 36 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE; 37 | 38 | private final Object LOCK = new Object(); 39 | 40 | private final Handler mUiHandler; 41 | 42 | private final AtomicInteger mNumOfThreads = new AtomicInteger(0); 43 | 44 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY); 45 | 46 | private final ThreadPoolExecutor mThreadPool; 47 | 48 | private int mNumOfFinishedConsumers; 49 | 50 | private int mNumOfReceivedMessages; 51 | 52 | private long mStartTimestamp; 53 | 54 | public ProducerConsumerBenchmarkUseCase(Handler uiHandler, ThreadPoolExecutor threadPool) { 55 | mUiHandler = uiHandler; 56 | mThreadPool = threadPool; 57 | } 58 | 59 | public void startBenchmarkAndNotify() { 60 | 61 | synchronized (LOCK) { 62 | mNumOfReceivedMessages = 0; 63 | mNumOfFinishedConsumers = 0; 64 | mStartTimestamp = System.currentTimeMillis(); 65 | mNumOfThreads.set(0); 66 | } 67 | 68 | // watcher-reporter thread 69 | mThreadPool.execute(() -> { 70 | synchronized (LOCK) { 71 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) { 72 | try { 73 | LOCK.wait(); 74 | } catch (InterruptedException e) { 75 | return; 76 | } 77 | } 78 | } 79 | notifySuccess(); 80 | }); 81 | 82 | // producers init thread 83 | mThreadPool.execute(() -> { 84 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 85 | startNewProducer(i); 86 | } 87 | }); 88 | 89 | // consumers init thread 90 | mThreadPool.execute(() -> { 91 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 92 | startNewConsumer(); 93 | } 94 | }); 95 | } 96 | 97 | 98 | private void startNewProducer(final int index) { 99 | mThreadPool.execute(() -> { 100 | try { 101 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS); 102 | } catch (InterruptedException e) { 103 | return; 104 | } 105 | mBlockingQueue.put(index); 106 | }); 107 | } 108 | 109 | private void startNewConsumer() { 110 | mThreadPool.execute(() -> { 111 | int message = mBlockingQueue.take(); 112 | synchronized (LOCK) { 113 | if (message != -1) { 114 | mNumOfReceivedMessages++; 115 | } 116 | mNumOfFinishedConsumers++; 117 | LOCK.notifyAll(); 118 | } 119 | }); 120 | } 121 | 122 | private void notifySuccess() { 123 | mUiHandler.post(() -> { 124 | Result result; 125 | synchronized (LOCK) { 126 | result = 127 | new Result( 128 | System.currentTimeMillis() - mStartTimestamp, 129 | mNumOfReceivedMessages 130 | ); 131 | } 132 | for (Listener listener : getListeners()) { 133 | listener.onBenchmarkCompleted(result); 134 | } 135 | }); 136 | } 137 | 138 | 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadposter/DesignWithThreadPosterDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthreadposter; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.ProgressBar; 9 | import android.widget.TextView; 10 | 11 | import com.techyourchance.multithreading.R; 12 | import com.techyourchance.multithreading.common.BaseFragment; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class DesignWithThreadPosterDemonstrationFragment extends BaseFragment implements ProducerConsumerBenchmarkUseCase.Listener { 19 | 20 | public static Fragment newInstance() { 21 | return new DesignWithThreadPosterDemonstrationFragment(); 22 | } 23 | 24 | private Button mBtnStart; 25 | private ProgressBar mProgressBar; 26 | private TextView mTxtReceivedMessagesCount; 27 | private TextView mTxtExecutionTime; 28 | 29 | private ProducerConsumerBenchmarkUseCase mProducerConsumerBenchmarkUseCase; 30 | 31 | @Override 32 | public void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | mProducerConsumerBenchmarkUseCase = new ProducerConsumerBenchmarkUseCase(); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 40 | View view = inflater.inflate(R.layout.fragment_design_with_thread_demonstration, container, false); 41 | 42 | mBtnStart = view.findViewById(R.id.btn_start); 43 | mProgressBar = view.findViewById(R.id.progress); 44 | mTxtReceivedMessagesCount = view.findViewById(R.id.txt_received_messages_count); 45 | mTxtExecutionTime = view.findViewById(R.id.txt_execution_time); 46 | 47 | mBtnStart.setOnClickListener(v -> { 48 | mBtnStart.setEnabled(false); 49 | mTxtReceivedMessagesCount.setText(""); 50 | mTxtExecutionTime.setText(""); 51 | mProgressBar.setVisibility(View.VISIBLE); 52 | 53 | mProducerConsumerBenchmarkUseCase.startBenchmarkAndNotify(); 54 | }); 55 | 56 | return view; 57 | } 58 | 59 | @Override 60 | protected String getScreenTitle() { 61 | return ""; 62 | } 63 | 64 | @Override 65 | public void onStart() { 66 | super.onStart(); 67 | mProducerConsumerBenchmarkUseCase.registerListener(this); 68 | } 69 | 70 | @Override 71 | public void onStop() { 72 | super.onStop(); 73 | mProducerConsumerBenchmarkUseCase.unregisterListener(this); 74 | } 75 | 76 | @Override 77 | public void onBenchmarkCompleted(ProducerConsumerBenchmarkUseCase.Result result) { 78 | mProgressBar.setVisibility(View.INVISIBLE); 79 | mBtnStart.setEnabled(true); 80 | mTxtReceivedMessagesCount.setText("Received messages: " + result.getNumOfReceivedMessages()); 81 | mTxtExecutionTime.setText("Execution time: " + result.getExecutionTime() + "ms"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadposter/MyBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthreadposter; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * Simplified implementation of blocking queue. 8 | */ 9 | class MyBlockingQueue { 10 | 11 | private final Object QUEUE_LOCK = new Object(); 12 | 13 | private final int mCapacity; 14 | private final Queue mQueue = new LinkedList<>(); 15 | 16 | private int mCurrentSize = 0; 17 | 18 | MyBlockingQueue(int capacity) { 19 | mCapacity = capacity; 20 | } 21 | 22 | /** 23 | * Inserts the specified element into this queue, waiting if necessary 24 | * for space to become available. 25 | * 26 | * @param number the element to add 27 | */ 28 | public void put(int number) { 29 | synchronized (QUEUE_LOCK) { 30 | while (mCurrentSize >= mCapacity) { 31 | try { 32 | QUEUE_LOCK.wait(); 33 | } catch (InterruptedException e) { 34 | return; 35 | } 36 | } 37 | 38 | mQueue.offer(number); 39 | mCurrentSize++; 40 | QUEUE_LOCK.notifyAll(); 41 | } 42 | } 43 | 44 | /** 45 | * Retrieves and removes the head of this queue, waiting if necessary 46 | * until an element becomes available. 47 | * 48 | * @return the head of this queue 49 | */ 50 | public int take() { 51 | synchronized (QUEUE_LOCK) { 52 | while (mCurrentSize <= 0) { 53 | try { 54 | QUEUE_LOCK.wait(); 55 | } catch (InterruptedException e) { 56 | return 0; 57 | } 58 | } 59 | mCurrentSize--; 60 | QUEUE_LOCK.notifyAll(); 61 | return mQueue.poll(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/designthreadposter/ProducerConsumerBenchmarkUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.designthreadposter; 2 | 3 | import com.techyourchance.multithreading.DefaultConfiguration; 4 | import com.techyourchance.multithreading.common.BaseObservable; 5 | import com.techyourchance.threadposter.BackgroundThreadPoster; 6 | import com.techyourchance.threadposter.UiThreadPoster; 7 | 8 | public class ProducerConsumerBenchmarkUseCase extends BaseObservable { 9 | 10 | public static interface Listener { 11 | void onBenchmarkCompleted(Result result); 12 | } 13 | 14 | public static class Result { 15 | private final long mExecutionTime; 16 | private final int mNumOfReceivedMessages; 17 | 18 | public Result(long executionTime, int numOfReceivedMessages) { 19 | mExecutionTime = executionTime; 20 | mNumOfReceivedMessages = numOfReceivedMessages; 21 | } 22 | 23 | public long getExecutionTime() { 24 | return mExecutionTime; 25 | } 26 | 27 | public int getNumOfReceivedMessages() { 28 | return mNumOfReceivedMessages; 29 | } 30 | } 31 | 32 | private static final int NUM_OF_MESSAGES = DefaultConfiguration.DEFAULT_NUM_OF_MESSAGES; 33 | private static final int BLOCKING_QUEUE_CAPACITY = DefaultConfiguration.DEFAULT_BLOCKING_QUEUE_SIZE; 34 | 35 | private final Object LOCK = new Object(); 36 | 37 | private final UiThreadPoster mUiThreadPoster = new UiThreadPoster(); 38 | private final BackgroundThreadPoster mBackgroundThreadPoster = new BackgroundThreadPoster(); 39 | 40 | private final MyBlockingQueue mBlockingQueue = new MyBlockingQueue(BLOCKING_QUEUE_CAPACITY); 41 | 42 | private int mNumOfFinishedConsumers; 43 | 44 | private int mNumOfReceivedMessages; 45 | 46 | private long mStartTimestamp; 47 | 48 | 49 | public void startBenchmarkAndNotify() { 50 | 51 | synchronized (LOCK) { 52 | mNumOfReceivedMessages = 0; 53 | mNumOfFinishedConsumers = 0; 54 | mStartTimestamp = System.currentTimeMillis(); 55 | } 56 | 57 | // watcher-reporter thread 58 | mBackgroundThreadPoster.post(() -> { 59 | synchronized (LOCK) { 60 | while (mNumOfFinishedConsumers < NUM_OF_MESSAGES) { 61 | try { 62 | LOCK.wait(); 63 | } catch (InterruptedException e) { 64 | return; 65 | } 66 | } 67 | } 68 | notifySuccess(); 69 | }); 70 | 71 | // producers init thread 72 | mBackgroundThreadPoster.post(() -> { 73 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 74 | startNewProducer(i); 75 | } 76 | }); 77 | 78 | // consumers init thread 79 | mBackgroundThreadPoster.post(() -> { 80 | for (int i = 0; i < NUM_OF_MESSAGES; i++) { 81 | startNewConsumer(); 82 | } 83 | }); 84 | } 85 | 86 | 87 | private void startNewProducer(final int index) { 88 | mBackgroundThreadPoster.post(() -> { 89 | try { 90 | Thread.sleep(DefaultConfiguration.DEFAULT_PRODUCER_DELAY_MS); 91 | } catch (InterruptedException e) { 92 | return; 93 | } 94 | mBlockingQueue.put(index); 95 | }); 96 | } 97 | 98 | private void startNewConsumer() { 99 | mBackgroundThreadPoster.post(() -> { 100 | int message = mBlockingQueue.take(); 101 | synchronized (LOCK) { 102 | if (message != -1) { 103 | mNumOfReceivedMessages++; 104 | } 105 | mNumOfFinishedConsumers++; 106 | LOCK.notifyAll(); 107 | } 108 | }); 109 | } 110 | 111 | private void notifySuccess() { 112 | mUiThreadPoster.post(() -> { 113 | Result result; 114 | synchronized (LOCK) { 115 | result = 116 | new Result( 117 | System.currentTimeMillis() - mStartTimestamp, 118 | mNumOfReceivedMessages 119 | ); 120 | } 121 | for (Listener listener : getListeners()) { 122 | listener.onBenchmarkCompleted(result); 123 | } 124 | }); 125 | } 126 | 127 | 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/synchronization/SynchronizationDemonstration.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.synchronization; 2 | 3 | public class SynchronizationDemonstration { 4 | 5 | private static final Object LOCK = new Object(); 6 | 7 | private static int sCount = 0; 8 | 9 | public static void main(String[] args) { 10 | new Consumer().start(); 11 | try { 12 | Thread.sleep(100); 13 | } catch (InterruptedException e) { 14 | return; 15 | } 16 | new Producer().start(); 17 | } 18 | 19 | static class Consumer extends Thread { 20 | @Override 21 | public void run() { 22 | int localValue = -1; 23 | while (true) { 24 | synchronized (LOCK) { 25 | if (localValue != sCount) { 26 | System.out.println("Consumer: detected count change " + sCount); 27 | localValue = sCount; 28 | } 29 | if (sCount >= 5) { 30 | break; 31 | } 32 | } 33 | } 34 | System.out.println("Consumer: terminating"); 35 | } 36 | } 37 | 38 | static class Producer extends Thread { 39 | @Override 40 | public void run() { 41 | while (true) { 42 | synchronized (LOCK) { 43 | if (sCount >= 5) { 44 | break; 45 | } 46 | int localValue = sCount; 47 | localValue++; 48 | System.out.println("Producer: incrementing count to " + localValue); 49 | sCount = localValue; 50 | } 51 | } 52 | System.out.println("Producer: terminating"); 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/uihandler/UiHandlerDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.uihandler; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Button; 12 | 13 | import com.techyourchance.multithreading.R; 14 | import com.techyourchance.multithreading.common.BaseFragment; 15 | 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | import androidx.fragment.app.Fragment; 19 | 20 | 21 | @SuppressLint("SetTextI18n") 22 | public class UiHandlerDemonstrationFragment extends BaseFragment { 23 | 24 | private static final int ITERATIONS_COUNTER_DURATION_SEC = 10; 25 | 26 | public static Fragment newInstance() { 27 | return new UiHandlerDemonstrationFragment(); 28 | } 29 | 30 | private Button mBtnCountIterations; 31 | 32 | private final Handler mUiHandler = new Handler(Looper.getMainLooper()); 33 | 34 | @Nullable 35 | @Override 36 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 37 | View view = inflater.inflate(R.layout.fragment_ui_handler_demonstration, container, false); 38 | 39 | mBtnCountIterations = view.findViewById(R.id.btn_count_iterations); 40 | mBtnCountIterations.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | countIterations(); 44 | } 45 | }); 46 | 47 | return view; 48 | } 49 | 50 | @Override 51 | protected String getScreenTitle() { 52 | return ""; 53 | } 54 | 55 | private void countIterations() { 56 | new Thread(new Runnable() { 57 | @Override 58 | public void run() { 59 | long startTimestamp = System.currentTimeMillis(); 60 | long endTimestamp = startTimestamp + ITERATIONS_COUNTER_DURATION_SEC * 1000; 61 | 62 | int iterationsCount = 0; 63 | while (System.currentTimeMillis() <= endTimestamp) { 64 | iterationsCount++; 65 | } 66 | 67 | final int iterationsCountFinal = iterationsCount; 68 | 69 | mUiHandler.post(new Runnable() { 70 | @Override 71 | public void run() { 72 | Log.d("UiHandler", "Current thread: " + Thread.currentThread().getName()); 73 | mBtnCountIterations.setText("Iterations: " + iterationsCountFinal); 74 | } 75 | }); 76 | } 77 | }).start(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/uithread/UiThreadDemonstrationFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.uithread; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | 10 | import com.techyourchance.multithreading.R; 11 | import com.techyourchance.multithreading.common.BaseFragment; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.fragment.app.Fragment; 16 | 17 | public class UiThreadDemonstrationFragment extends BaseFragment { 18 | 19 | private static final String TAG = "UiThreadDemonstration"; 20 | 21 | public static Fragment newInstance() { 22 | return new UiThreadDemonstrationFragment(); 23 | } 24 | 25 | @Override 26 | public void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | logThreadInfo("onCreate()"); 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 34 | View view = inflater.inflate(R.layout.fragment_ui_thread_demonstration, container, false); 35 | 36 | Button mBtnCallbackCheck = view.findViewById(R.id.btn_callback_check); 37 | mBtnCallbackCheck.setOnClickListener(new View.OnClickListener() { 38 | @Override 39 | public void onClick(View v) { 40 | logThreadInfo("button callback"); 41 | } 42 | }); 43 | 44 | return view; 45 | } 46 | 47 | @Override 48 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 49 | super.onViewCreated(view, savedInstanceState); 50 | logThreadInfo("onViewCreated()"); 51 | } 52 | 53 | @Override 54 | public void onStart() { 55 | super.onStart(); 56 | logThreadInfo("onStart()"); 57 | } 58 | 59 | @Override 60 | public void onResume() { 61 | super.onResume(); 62 | logThreadInfo("onResume()"); 63 | } 64 | 65 | @Override 66 | public void onPause() { 67 | super.onPause(); 68 | logThreadInfo("onPause()"); 69 | } 70 | 71 | @Override 72 | public void onStop() { 73 | super.onStop(); 74 | logThreadInfo("onStop()"); 75 | } 76 | 77 | @Override 78 | public void onDestroyView() { 79 | super.onDestroyView(); 80 | logThreadInfo("onDestroyView()"); 81 | } 82 | 83 | @Override 84 | public void onDestroy() { 85 | super.onDestroy(); 86 | logThreadInfo("onDestroy()"); 87 | } 88 | 89 | @Override 90 | protected String getScreenTitle() { 91 | return ""; 92 | } 93 | 94 | private void logThreadInfo(String eventName) { 95 | Log.d(TAG, "event\n" 96 | + eventName 97 | + "; thread name: " + Thread.currentThread().getName() 98 | + "; thread ID: " + Thread.currentThread().getId()); 99 | 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/demonstrations/visibility/VisibilityDemonstration.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.demonstrations.visibility; 2 | 3 | public class VisibilityDemonstration { 4 | 5 | private static int sCount = 0; 6 | 7 | public static void main(String[] args) { 8 | new Consumer().start(); 9 | try { 10 | Thread.sleep(100); 11 | } catch (InterruptedException e) { 12 | return; 13 | } 14 | new Producer().start(); 15 | } 16 | 17 | static class Consumer extends Thread { 18 | @Override 19 | public void run() { 20 | int localValue = -1; 21 | while (true) { 22 | if (localValue != sCount) { 23 | System.out.println("Consumer: detected count change " + sCount); 24 | localValue = sCount; 25 | } 26 | if (sCount >= 5) { 27 | break; 28 | } 29 | } 30 | System.out.println("Consumer: terminating"); 31 | } 32 | } 33 | 34 | static class Producer extends Thread { 35 | @Override 36 | public void run() { 37 | while (sCount < 5) { 38 | int localValue = sCount; 39 | localValue++; 40 | System.out.println("Producer: incrementing count to " + localValue); 41 | sCount = localValue; 42 | try { 43 | Thread.sleep(1000); 44 | } catch (InterruptedException e) { 45 | return; 46 | } 47 | } 48 | System.out.println("Producer: terminating"); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise1/Exercise1Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise1; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | import android.widget.ListView; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | 13 | import com.techyourchance.multithreading.R; 14 | import com.techyourchance.multithreading.common.BaseFragment; 15 | import com.techyourchance.multithreading.common.ScreensNavigator; 16 | import com.techyourchance.multithreading.home.HomeArrayAdapter; 17 | import com.techyourchance.multithreading.home.ScreenReachableFromHome; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | 23 | public class Exercise1Fragment extends BaseFragment { 24 | 25 | private static final int ITERATIONS_COUNTER_DURATION_SEC = 10; 26 | 27 | public static Fragment newInstance() { 28 | return new Exercise1Fragment(); 29 | } 30 | 31 | private Button mBtnCountIterations; 32 | 33 | @Nullable 34 | @Override 35 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 36 | View view = inflater.inflate(R.layout.fragment_exercise_1, container, false); 37 | 38 | mBtnCountIterations = view.findViewById(R.id.btn_count_iterations); 39 | mBtnCountIterations.setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | countIterations(); 43 | } 44 | }); 45 | 46 | return view; 47 | } 48 | 49 | @Override 50 | protected String getScreenTitle() { 51 | return "Exercise 1"; 52 | } 53 | 54 | private void countIterations() { 55 | long startTimestamp = System.currentTimeMillis(); 56 | long endTimestamp = startTimestamp + ITERATIONS_COUNTER_DURATION_SEC * 1000; 57 | 58 | int iterationsCount = 0; 59 | while (System.currentTimeMillis() <= endTimestamp) { 60 | iterationsCount++; 61 | } 62 | 63 | Log.d( 64 | "Exercise1", 65 | "iterations in " + ITERATIONS_COUNTER_DURATION_SEC + "seconds: " + iterationsCount 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise10/Exercise10Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise10 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.inputmethod.InputMethodManager 9 | import android.widget.Button 10 | import android.widget.EditText 11 | import android.widget.TextView 12 | 13 | import com.techyourchance.multithreading.R 14 | import com.techyourchance.multithreading.common.BaseFragment 15 | 16 | import java.math.BigInteger 17 | import androidx.fragment.app.Fragment 18 | import com.techyourchance.multithreading.DefaultConfiguration 19 | 20 | class Exercise10Fragment : BaseFragment(), ComputeFactorialUseCase.Listener { 21 | 22 | private lateinit var edtArgument: EditText 23 | private lateinit var edtTimeout: EditText 24 | private lateinit var btnStartWork: Button 25 | private lateinit var txtResult: TextView 26 | 27 | private lateinit var computeFactorialUseCase: ComputeFactorialUseCase 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | computeFactorialUseCase = ComputeFactorialUseCase() 32 | } 33 | 34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 35 | val view = inflater.inflate(R.layout.fragment_exercise_10, container, false) 36 | 37 | view.apply { 38 | edtArgument = findViewById(R.id.edt_argument) 39 | edtTimeout = findViewById(R.id.edt_timeout) 40 | btnStartWork = findViewById(R.id.btn_compute) 41 | txtResult = findViewById(R.id.txt_result) 42 | } 43 | 44 | btnStartWork.setOnClickListener { _ -> 45 | if (edtArgument.text.toString().isEmpty()) { 46 | return@setOnClickListener 47 | } 48 | 49 | txtResult.text = "" 50 | btnStartWork.isEnabled = false 51 | 52 | 53 | val imm = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 54 | imm.hideSoftInputFromWindow(btnStartWork.windowToken, 0) 55 | 56 | val argument = Integer.valueOf(edtArgument.text.toString()) 57 | 58 | computeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()) 59 | } 60 | 61 | return view 62 | } 63 | 64 | override fun onStart() { 65 | super.onStart() 66 | computeFactorialUseCase.registerListener(this) 67 | } 68 | 69 | override fun onStop() { 70 | super.onStop() 71 | computeFactorialUseCase.unregisterListener(this) 72 | 73 | } 74 | 75 | override fun getScreenTitle(): String { 76 | return "Exercise 10" 77 | } 78 | 79 | override fun onFactorialComputed(result: BigInteger) { 80 | txtResult.text = result.toString() 81 | btnStartWork.isEnabled = true 82 | } 83 | 84 | override fun onFactorialComputationTimedOut() { 85 | txtResult.text = "Computation timed out" 86 | btnStartWork.isEnabled = true 87 | } 88 | 89 | override fun onFactorialComputationAborted() { 90 | txtResult.text = "Computation aborted" 91 | btnStartWork.isEnabled = true 92 | } 93 | 94 | private fun getTimeout() : Int { 95 | var timeout: Int 96 | if (edtTimeout.text.toString().isEmpty()) { 97 | timeout = MAX_TIMEOUT_MS 98 | } else { 99 | timeout = Integer.valueOf(edtTimeout.text.toString()) 100 | if (timeout > MAX_TIMEOUT_MS) { 101 | timeout = MAX_TIMEOUT_MS 102 | } 103 | } 104 | return timeout 105 | } 106 | 107 | companion object { 108 | fun newInstance(): Fragment { 109 | return Exercise10Fragment() 110 | } 111 | private const val MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise10/tips.txt: -------------------------------------------------------------------------------- 1 | Approach exercise 10 in three steps: 2 | 3 | Step 1: remove custom implementation of Observer pattern and wrap the current implementation of factorial computation algorithm in coroutine 4 | Step 2: refactor the internal implementation of the use case to coroutines, but keep the timeout detection logic intact 5 | Step 3: replace custom timeout detection logic with a call to "withTimeout" suspending function 6 | 7 | Hints for the solution: 8 | 1. The idea behind structured concurrency is to make your multithreaded code look sequential. Therefore, "clean" solution with coroutines shouldn't have any class-level properties (no shared mutable state). 9 | 2. Without mutable shared state, the solution should look similar to this (in principle, not in details): 10 | 11 | val intermediateResult1 = intermediateResult1() 12 | val intermediateResult2 = intermediateResult2(intermediateResult1) 13 | val result = computeFinalResult(intermediateResult2) 14 | 15 | 3. You might want to implement function with this signature in your solution: 16 | 17 | private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred = async(Dispatchers.IO) { 18 | } 19 | 20 | 4. Note that top level suspending function will need to return either success or timeout to Fragment. You can use sealed class for the return type. 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise2/Exercise2Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise2; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | 10 | import com.techyourchance.multithreading.R; 11 | import com.techyourchance.multithreading.common.BaseFragment; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.core.content.ContextCompat; 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class Exercise2Fragment extends BaseFragment { 19 | 20 | public static Fragment newInstance() { 21 | return new Exercise2Fragment(); 22 | } 23 | 24 | private byte[] mDummyData; 25 | 26 | @Nullable 27 | @Override 28 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 29 | mDummyData = new byte[50 * 1000 * 1000]; 30 | return inflater.inflate(R.layout.fragment_exercise_2, container, false); 31 | } 32 | 33 | @Override 34 | public void onStart() { 35 | super.onStart(); 36 | countScreenTime(); 37 | } 38 | 39 | @Override 40 | public void onStop() { 41 | super.onStop(); 42 | } 43 | 44 | @Override 45 | protected String getScreenTitle() { 46 | return "Exercise 2"; 47 | } 48 | 49 | private void countScreenTime() { 50 | new Thread(new Runnable() { 51 | @Override 52 | public void run() { 53 | int screenTimeSeconds = 0; 54 | while (true) { 55 | try { 56 | Thread.sleep(1000); 57 | } catch (InterruptedException e) { 58 | return; 59 | } 60 | screenTimeSeconds++; 61 | Log.d("Exercise 2", "screen time: " + screenTimeSeconds + "s"); 62 | } 63 | } 64 | }).start(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise3/Exercise3Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise3; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import com.techyourchance.multithreading.R; 11 | import com.techyourchance.multithreading.common.BaseFragment; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.fragment.app.Fragment; 16 | 17 | public class Exercise3Fragment extends BaseFragment { 18 | 19 | private static final int SECONDS_TO_COUNT = 3; 20 | 21 | public static Fragment newInstance() { 22 | return new Exercise3Fragment(); 23 | } 24 | 25 | private Button mBtnCountSeconds; 26 | private TextView mTxtCount; 27 | 28 | @Nullable 29 | @Override 30 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 31 | View view = inflater.inflate(R.layout.fragment_exercise_3, container, false); 32 | 33 | mBtnCountSeconds = view.findViewById(R.id.btn_count_seconds); 34 | mTxtCount = view.findViewById(R.id.txt_count); 35 | 36 | mBtnCountSeconds.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View v) { 39 | countIterations(); 40 | } 41 | }); 42 | 43 | return view; 44 | } 45 | 46 | @Override 47 | protected String getScreenTitle() { 48 | return "Exercise 3"; 49 | } 50 | 51 | private void countIterations() { 52 | /* 53 | 1. Disable button to prevent multiple clicks 54 | 2. Start counting on background thread using loop and Thread.sleep() 55 | 3. Show count in TextView 56 | 4. When count completes, show "done" in TextView and enable the button 57 | */ 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise7/Exercise7Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise7; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | 17 | import java.math.BigInteger; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | 23 | public class Exercise7Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener { 24 | 25 | public static Fragment newInstance() { 26 | return new Exercise7Fragment(); 27 | } 28 | 29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 30 | 31 | private EditText mEdtArgument; 32 | private EditText mEdtTimeout; 33 | private Button mBtnStartWork; 34 | private TextView mTxtResult; 35 | 36 | private ComputeFactorialUseCase mComputeFactorialUseCase; 37 | 38 | @Override 39 | public void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | mComputeFactorialUseCase = new ComputeFactorialUseCase(); 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View view = inflater.inflate(R.layout.fragment_exercise_7, container, false); 48 | 49 | mEdtArgument = view.findViewById(R.id.edt_argument); 50 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 51 | mBtnStartWork = view.findViewById(R.id.btn_compute); 52 | mTxtResult = view.findViewById(R.id.txt_result); 53 | 54 | mBtnStartWork.setOnClickListener(v -> { 55 | if (mEdtArgument.getText().toString().isEmpty()) { 56 | return; 57 | } 58 | 59 | mTxtResult.setText(""); 60 | mBtnStartWork.setEnabled(false); 61 | 62 | 63 | InputMethodManager imm = 64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 66 | 67 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 68 | 69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()); 70 | }); 71 | 72 | return view; 73 | } 74 | 75 | @Override 76 | public void onStart() { 77 | super.onStart(); 78 | mComputeFactorialUseCase.registerListener(this); 79 | } 80 | 81 | @Override 82 | public void onStop() { 83 | super.onStop(); 84 | mComputeFactorialUseCase.unregisterListener(this); 85 | 86 | } 87 | 88 | @Override 89 | protected String getScreenTitle() { 90 | return "Exercise 7"; 91 | } 92 | 93 | private int getTimeout() { 94 | int timeout; 95 | if (mEdtTimeout.getText().toString().isEmpty()) { 96 | timeout = MAX_TIMEOUT_MS; 97 | } else { 98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 99 | if (timeout > MAX_TIMEOUT_MS) { 100 | timeout = MAX_TIMEOUT_MS; 101 | } 102 | } 103 | return timeout; 104 | } 105 | 106 | @Override 107 | public void onFactorialComputed(BigInteger result) { 108 | mTxtResult.setText(result.toString()); 109 | mBtnStartWork.setEnabled(true); 110 | } 111 | 112 | @Override 113 | public void onFactorialComputationTimedOut() { 114 | mTxtResult.setText("Computation timed out"); 115 | mBtnStartWork.setEnabled(true); 116 | } 117 | 118 | @Override 119 | public void onFactorialComputationAborted() { 120 | mTxtResult.setText("Computation aborted"); 121 | mBtnStartWork.setEnabled(true); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise8/Exercise8Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise8; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | 17 | import java.math.BigInteger; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | 23 | public class Exercise8Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener { 24 | 25 | public static Fragment newInstance() { 26 | return new Exercise8Fragment(); 27 | } 28 | 29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 30 | 31 | private EditText mEdtArgument; 32 | private EditText mEdtTimeout; 33 | private Button mBtnStartWork; 34 | private TextView mTxtResult; 35 | 36 | private ComputeFactorialUseCase mComputeFactorialUseCase; 37 | 38 | @Override 39 | public void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | mComputeFactorialUseCase = new ComputeFactorialUseCase(); 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View view = inflater.inflate(R.layout.fragment_exercise_8, container, false); 48 | 49 | mEdtArgument = view.findViewById(R.id.edt_argument); 50 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 51 | mBtnStartWork = view.findViewById(R.id.btn_compute); 52 | mTxtResult = view.findViewById(R.id.txt_result); 53 | 54 | mBtnStartWork.setOnClickListener(v -> { 55 | if (mEdtArgument.getText().toString().isEmpty()) { 56 | return; 57 | } 58 | 59 | mTxtResult.setText(""); 60 | mBtnStartWork.setEnabled(false); 61 | 62 | 63 | InputMethodManager imm = 64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 66 | 67 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 68 | 69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()); 70 | }); 71 | 72 | return view; 73 | } 74 | 75 | @Override 76 | public void onStart() { 77 | super.onStart(); 78 | mComputeFactorialUseCase.registerListener(this); 79 | } 80 | 81 | @Override 82 | public void onStop() { 83 | super.onStop(); 84 | mComputeFactorialUseCase.unregisterListener(this); 85 | 86 | } 87 | 88 | @Override 89 | protected String getScreenTitle() { 90 | return "Exercise 8"; 91 | } 92 | 93 | private int getTimeout() { 94 | int timeout; 95 | if (mEdtTimeout.getText().toString().isEmpty()) { 96 | timeout = MAX_TIMEOUT_MS; 97 | } else { 98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 99 | if (timeout > MAX_TIMEOUT_MS) { 100 | timeout = MAX_TIMEOUT_MS; 101 | } 102 | } 103 | return timeout; 104 | } 105 | 106 | @Override 107 | public void onFactorialComputed(BigInteger result) { 108 | mTxtResult.setText(result.toString()); 109 | mBtnStartWork.setEnabled(true); 110 | } 111 | 112 | @Override 113 | public void onFactorialComputationTimedOut() { 114 | mTxtResult.setText("Computation timed out"); 115 | mBtnStartWork.setEnabled(true); 116 | } 117 | 118 | @Override 119 | public void onFactorialComputationAborted() { 120 | mTxtResult.setText("Computation aborted"); 121 | mBtnStartWork.setEnabled(true); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/exercises/exercise9/Exercise9Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.exercises.exercise9; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | 17 | import java.math.BigInteger; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | 23 | public class Exercise9Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener { 24 | 25 | public static Fragment newInstance() { 26 | return new Exercise9Fragment(); 27 | } 28 | 29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 30 | 31 | private EditText mEdtArgument; 32 | private EditText mEdtTimeout; 33 | private Button mBtnStartWork; 34 | private TextView mTxtResult; 35 | 36 | private ComputeFactorialUseCase mComputeFactorialUseCase; 37 | 38 | @Override 39 | public void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | mComputeFactorialUseCase = new ComputeFactorialUseCase(); 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View view = inflater.inflate(R.layout.fragment_exercise_9, container, false); 48 | 49 | mEdtArgument = view.findViewById(R.id.edt_argument); 50 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 51 | mBtnStartWork = view.findViewById(R.id.btn_compute); 52 | mTxtResult = view.findViewById(R.id.txt_result); 53 | 54 | mBtnStartWork.setOnClickListener(v -> { 55 | if (mEdtArgument.getText().toString().isEmpty()) { 56 | return; 57 | } 58 | 59 | mTxtResult.setText(""); 60 | mBtnStartWork.setEnabled(false); 61 | 62 | 63 | InputMethodManager imm = 64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 66 | 67 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 68 | 69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()); 70 | }); 71 | 72 | return view; 73 | } 74 | 75 | @Override 76 | public void onStart() { 77 | super.onStart(); 78 | mComputeFactorialUseCase.registerListener(this); 79 | } 80 | 81 | @Override 82 | public void onStop() { 83 | super.onStop(); 84 | mComputeFactorialUseCase.unregisterListener(this); 85 | 86 | } 87 | 88 | @Override 89 | protected String getScreenTitle() { 90 | return "Exercise 9"; 91 | } 92 | 93 | private int getTimeout() { 94 | int timeout; 95 | if (mEdtTimeout.getText().toString().isEmpty()) { 96 | timeout = MAX_TIMEOUT_MS; 97 | } else { 98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 99 | if (timeout > MAX_TIMEOUT_MS) { 100 | timeout = MAX_TIMEOUT_MS; 101 | } 102 | } 103 | return timeout; 104 | } 105 | 106 | @Override 107 | public void onFactorialComputed(BigInteger result) { 108 | mTxtResult.setText(result.toString()); 109 | mBtnStartWork.setEnabled(true); 110 | } 111 | 112 | @Override 113 | public void onFactorialComputationTimedOut() { 114 | mTxtResult.setText("Computation timed out"); 115 | mBtnStartWork.setEnabled(true); 116 | } 117 | 118 | @Override 119 | public void onFactorialComputationAborted() { 120 | mTxtResult.setText("Computation aborted"); 121 | mBtnStartWork.setEnabled(true); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/home/HomeArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.home; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.TextView; 9 | 10 | import com.techyourchance.multithreading.R; 11 | 12 | import androidx.annotation.NonNull; 13 | 14 | public class HomeArrayAdapter extends ArrayAdapter { 15 | 16 | public interface Listener { 17 | void onScreenClicked(ScreenReachableFromHome screenReachableFromHome); 18 | } 19 | 20 | private final Listener mListener; 21 | 22 | public HomeArrayAdapter(@NonNull Context context, Listener listener) { 23 | super(context, 0); 24 | mListener = listener; 25 | } 26 | 27 | @Override 28 | public View getView(int position, View convertView, ViewGroup parent) { 29 | if (convertView == null) { 30 | convertView = LayoutInflater.from(getContext()) 31 | .inflate(R.layout.list_item_screen_reachable_from_home, parent, false); 32 | } 33 | 34 | final ScreenReachableFromHome screenReachableFromHome = getItem(position); 35 | 36 | // display screen name 37 | TextView txtName = convertView.findViewById(R.id.txt_screen_name); 38 | txtName.setText(screenReachableFromHome.getName()); 39 | 40 | // set click listener on individual item view 41 | convertView.setOnClickListener(new View.OnClickListener() { 42 | @Override 43 | public void onClick(View v) { 44 | mListener.onScreenClicked(screenReachableFromHome); 45 | } 46 | }); 47 | 48 | return convertView; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/home/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.home; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ListView; 8 | 9 | import com.techyourchance.multithreading.common.BaseFragment; 10 | import com.techyourchance.multithreading.R; 11 | import com.techyourchance.multithreading.common.ScreensNavigator; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.fragment.app.Fragment; 16 | 17 | public class HomeFragment extends BaseFragment implements HomeArrayAdapter.Listener { 18 | 19 | public static Fragment newInstance() { 20 | return new HomeFragment(); 21 | } 22 | 23 | private ScreensNavigator mScreensNavigator; 24 | 25 | private ListView mListScreensReachableFromHome; 26 | private HomeArrayAdapter mAdapterScreensReachableFromHome; 27 | 28 | @Nullable 29 | @Override 30 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 31 | View view = inflater.inflate(R.layout.fragment_home, container, false); 32 | 33 | mScreensNavigator = getCompositionRoot().getScreensNavigator(); 34 | 35 | mAdapterScreensReachableFromHome = new HomeArrayAdapter(requireContext(), this); 36 | mListScreensReachableFromHome = view.findViewById(R.id.list_screens); 37 | mListScreensReachableFromHome.setAdapter(mAdapterScreensReachableFromHome); 38 | 39 | mAdapterScreensReachableFromHome.addAll(ScreenReachableFromHome.values()); 40 | mAdapterScreensReachableFromHome.notifyDataSetChanged(); 41 | 42 | return view; 43 | } 44 | 45 | @Override 46 | protected String getScreenTitle() { 47 | return "Home Screen"; 48 | } 49 | 50 | @Nullable 51 | @Override 52 | public Fragment getHierarchicalParentFragment() { 53 | return null; 54 | } 55 | 56 | @Override 57 | public void onScreenClicked(ScreenReachableFromHome screenReachableFromHome) { 58 | switch (screenReachableFromHome) { 59 | case EXERCISE_1: 60 | mScreensNavigator.toExercise1Screen(); 61 | break; 62 | case EXERCISE_2: 63 | mScreensNavigator.toExercise2Screen(); 64 | break; 65 | case UI_THREAD_DEMONSTRATION: 66 | mScreensNavigator.toUiThreadDemonstration(); 67 | break; 68 | case UI_HANDLER_DEMONSTRATION: 69 | mScreensNavigator.toUiHandlerDemonstration(); 70 | break; 71 | case CUSTOM_HANDLER_DEMONSTRATION: 72 | mScreensNavigator.toCustomHandlerDemonstration(); 73 | break; 74 | case EXERCISE_3: 75 | mScreensNavigator.toExercise3Screen(); 76 | break; 77 | case ATOMICITY_DEMONSTRATION: 78 | mScreensNavigator.toAtomicityDemonstration(); 79 | break; 80 | case EXERCISE_4: 81 | mScreensNavigator.toExercise4Screen(); 82 | break; 83 | case THREAD_WAIT_DEMONSTRATION: 84 | mScreensNavigator.toThreadWaitDemonstration(); 85 | break; 86 | case EXERCISE_5: 87 | mScreensNavigator.toExercise5Screen(); 88 | break; 89 | case DESIGN_WITH_THREADS_DEMONSTRATION: 90 | mScreensNavigator.toDesignWithThreadsDemonstration(); 91 | break; 92 | case EXERCISE_6: 93 | mScreensNavigator.toExercise6Screen(); 94 | break; 95 | case DESIGN_WITH_THREAD_POOL_DEMONSTRATION: 96 | mScreensNavigator.toDesignWithThreadPoolDemonstration(); 97 | break; 98 | case EXERCISE_7: 99 | mScreensNavigator.toExercise7Screen(); 100 | break; 101 | case DESIGN_WITH_ASYNCTASK_DEMONSTRATION: 102 | mScreensNavigator.toDesignWithAsyncTaskDemonstration(); 103 | break; 104 | case DESIGN_WITH_THREAD_POSTER_DEMONSTRATION: 105 | mScreensNavigator.toThreadPosterDemonstration(); 106 | break; 107 | case EXERCISE_8: 108 | mScreensNavigator.toExercise8Screen(); 109 | break; 110 | case DESIGN_WITH_RX_JAVA_DEMONSTRATION: 111 | mScreensNavigator.toDesignWithRxJavaDemonstration(); 112 | break; 113 | case EXERCISE_9: 114 | mScreensNavigator.toExercise9Screen(); 115 | break; 116 | case DESIGN_WITH_COROUTINES_DEMONSTRATION: 117 | mScreensNavigator.toDesignWithCoroutinesDemonstration(); 118 | break; 119 | case EXERCISE_10: 120 | mScreensNavigator.toExercise10Screen(); 121 | break; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/home/ScreenReachableFromHome.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.home; 2 | 3 | public enum ScreenReachableFromHome { 4 | EXERCISE_1("Exercise 1"), 5 | EXERCISE_2("Exercise 2"), 6 | UI_THREAD_DEMONSTRATION("UI Thread Demo"), 7 | UI_HANDLER_DEMONSTRATION("UI Handler Demo"), 8 | CUSTOM_HANDLER_DEMONSTRATION("Custom Handler Demo"), 9 | EXERCISE_3("Exercise 3"), 10 | ATOMICITY_DEMONSTRATION("Atomicity Demo"), 11 | EXERCISE_4("Exercise 4"), 12 | THREAD_WAIT_DEMONSTRATION("Thread Wait Demo"), 13 | EXERCISE_5("Exercise 5"), 14 | DESIGN_WITH_THREADS_DEMONSTRATION("Design Demo: Threads"), 15 | EXERCISE_6("Exercise 6"), 16 | DESIGN_WITH_THREAD_POOL_DEMONSTRATION("Design Demo: Thread Pool"), 17 | EXERCISE_7("Exercise 7"), 18 | DESIGN_WITH_ASYNCTASK_DEMONSTRATION("Design Demo: AsyncTask"), 19 | DESIGN_WITH_THREAD_POSTER_DEMONSTRATION("Design Demo: ThreadPoster"), 20 | EXERCISE_8("Exercise 8"), 21 | DESIGN_WITH_RX_JAVA_DEMONSTRATION("Design Demo: RxJava"), 22 | EXERCISE_9("Exercise 9"), 23 | DESIGN_WITH_COROUTINES_DEMONSTRATION("Design Demo: Coroutines"), 24 | EXERCISE_10("Exercise 10"), 25 | ; 26 | 27 | private String mName; 28 | 29 | ScreenReachableFromHome(String name) { 30 | mName = name; 31 | } 32 | 33 | public String getName() { 34 | return mName; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise1/SolutionExercise1Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise1; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | 10 | import com.techyourchance.multithreading.R; 11 | import com.techyourchance.multithreading.common.BaseFragment; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.fragment.app.Fragment; 16 | 17 | public class SolutionExercise1Fragment extends BaseFragment { 18 | 19 | private static final int ITERATIONS_COUNTER_DURATION_SEC = 10; 20 | 21 | public static Fragment newInstance() { 22 | return new SolutionExercise1Fragment(); 23 | } 24 | 25 | private Button mBtnCountIterations; 26 | 27 | @Nullable 28 | @Override 29 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 30 | View view = inflater.inflate(R.layout.fragment_exercise_1, container, false); 31 | 32 | mBtnCountIterations = view.findViewById(R.id.btn_count_iterations); 33 | mBtnCountIterations.setOnClickListener(new View.OnClickListener() { 34 | @Override 35 | public void onClick(View v) { 36 | countIterations(); 37 | } 38 | }); 39 | 40 | return view; 41 | } 42 | 43 | @Override 44 | protected String getScreenTitle() { 45 | return "Exercise 1"; 46 | } 47 | 48 | private void countIterations() { 49 | new Thread(new Runnable() { 50 | @Override 51 | public void run() { 52 | long startTimestamp = System.currentTimeMillis(); 53 | long endTimestamp = startTimestamp + ITERATIONS_COUNTER_DURATION_SEC * 1000; 54 | 55 | int iterationsCount = 0; 56 | while (System.currentTimeMillis() <= endTimestamp) { 57 | iterationsCount++; 58 | } 59 | 60 | Log.d( 61 | "Exercise1", 62 | "iterations in " + ITERATIONS_COUNTER_DURATION_SEC + "seconds: " + iterationsCount 63 | ); 64 | } 65 | }).start(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise10/ComputeFactorialUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise10 2 | 3 | import java.math.BigInteger 4 | 5 | import androidx.annotation.WorkerThread 6 | import kotlinx.coroutines.* 7 | import java.util.concurrent.TimeUnit 8 | 9 | class ComputeFactorialUseCase { 10 | 11 | public sealed class Result { 12 | class Success(val result: BigInteger) : Result() 13 | object Timeout : Result() 14 | } 15 | 16 | suspend fun computeFactorial(argument: Int, timeout: Int) : Result { 17 | 18 | return withContext(Dispatchers.IO) { 19 | 20 | try { 21 | withTimeout(timeMillis = timeout.toLong()) { 22 | 23 | val computationRanges = getComputationRanges(argument) 24 | 25 | val partialProductsForRanges = computePartialProducts(computationRanges) 26 | 27 | val result = computeFinalResult(partialProductsForRanges) 28 | 29 | Result.Success(result) 30 | } 31 | } catch (e : TimeoutCancellationException) { 32 | Result.Timeout 33 | } 34 | 35 | } 36 | } 37 | 38 | private fun getComputationRanges(factorialArgument: Int) : Array { 39 | val numberOfThreads = getNumberOfThreads(factorialArgument) 40 | 41 | val threadsComputationRanges = Array(numberOfThreads) { ComputationRange(0, 0) } 42 | 43 | val computationRangeSize = factorialArgument / numberOfThreads 44 | 45 | var nextComputationRangeEnd = factorialArgument.toLong() 46 | 47 | for (i in numberOfThreads - 1 downTo 0) { 48 | threadsComputationRanges[i] = ComputationRange( 49 | nextComputationRangeEnd - computationRangeSize + 1, 50 | nextComputationRangeEnd 51 | ) 52 | nextComputationRangeEnd = threadsComputationRanges[i].start - 1 53 | } 54 | 55 | // add potentially "remaining" values to first thread's range 56 | threadsComputationRanges[0] = ComputationRange(1, threadsComputationRanges[0].end) 57 | 58 | return threadsComputationRanges 59 | } 60 | 61 | private fun getNumberOfThreads(factorialArgument: Int): Int { 62 | return if (factorialArgument < 20) 63 | 1 64 | else 65 | Runtime.getRuntime().availableProcessors() 66 | } 67 | 68 | private suspend fun computePartialProducts(computationRanges: Array) : List = coroutineScope { 69 | return@coroutineScope withContext(Dispatchers.IO) { 70 | return@withContext computationRanges.map { 71 | computeProductForRangeAsync(it) 72 | }.awaitAll() 73 | } 74 | } 75 | 76 | private fun CoroutineScope.computeProductForRangeAsync(computationRange: ComputationRange) : Deferred = async(Dispatchers.IO) { 77 | val rangeStart = computationRange.start 78 | val rangeEnd = computationRange.end 79 | 80 | var product = BigInteger("1") 81 | for (num in rangeStart..rangeEnd) { 82 | if (!isActive) { 83 | break 84 | } 85 | product = product.multiply(BigInteger(num.toString())) 86 | } 87 | 88 | return@async product 89 | } 90 | 91 | private suspend fun computeFinalResult(partialProducts: List): BigInteger = withContext(Dispatchers.IO) { 92 | var result = BigInteger("1") 93 | for (partialProduct in partialProducts) { 94 | if (!isActive) { 95 | break 96 | } 97 | result = result.multiply(partialProduct) 98 | } 99 | return@withContext result 100 | } 101 | 102 | private data class ComputationRange(val start: Long, val end: Long) 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise10/Exercise10Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise10 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.inputmethod.InputMethodManager 9 | import android.widget.Button 10 | import android.widget.EditText 11 | import android.widget.TextView 12 | 13 | import com.techyourchance.multithreading.R 14 | import com.techyourchance.multithreading.common.BaseFragment 15 | 16 | import java.math.BigInteger 17 | import androidx.fragment.app.Fragment 18 | import com.techyourchance.multithreading.DefaultConfiguration 19 | import kotlinx.coroutines.CoroutineScope 20 | import kotlinx.coroutines.Dispatchers 21 | import kotlinx.coroutines.Job 22 | import kotlinx.coroutines.launch 23 | 24 | class SolutionExercise10Fragment : BaseFragment() { 25 | 26 | private lateinit var edtArgument: EditText 27 | private lateinit var edtTimeout: EditText 28 | private lateinit var btnStartWork: Button 29 | private lateinit var txtResult: TextView 30 | 31 | private lateinit var computeFactorialUseCase: ComputeFactorialUseCase 32 | 33 | private var job : Job? = null 34 | 35 | override fun onCreate(savedInstanceState: Bundle?) { 36 | super.onCreate(savedInstanceState) 37 | computeFactorialUseCase = ComputeFactorialUseCase() 38 | } 39 | 40 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 41 | val view = inflater.inflate(R.layout.fragment_exercise_10, container, false) 42 | 43 | view.apply { 44 | edtArgument = findViewById(R.id.edt_argument) 45 | edtTimeout = findViewById(R.id.edt_timeout) 46 | btnStartWork = findViewById(R.id.btn_compute) 47 | txtResult = findViewById(R.id.txt_result) 48 | } 49 | 50 | btnStartWork.setOnClickListener { _ -> 51 | if (edtArgument.text.toString().isEmpty()) { 52 | return@setOnClickListener 53 | } 54 | 55 | txtResult.text = "" 56 | btnStartWork.isEnabled = false 57 | 58 | 59 | val imm = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 60 | imm.hideSoftInputFromWindow(btnStartWork.windowToken, 0) 61 | 62 | val argument = Integer.valueOf(edtArgument.text.toString()) 63 | 64 | job = CoroutineScope(Dispatchers.Main).launch { 65 | when (val result = computeFactorialUseCase.computeFactorial(argument, getTimeout())) { 66 | is ComputeFactorialUseCase.Result.Success -> onFactorialComputed(result.result) 67 | is ComputeFactorialUseCase.Result.Timeout -> onFactorialComputationTimedOut() 68 | } 69 | } 70 | } 71 | 72 | return view 73 | } 74 | 75 | override fun onStart() { 76 | super.onStart() 77 | } 78 | 79 | override fun onStop() { 80 | super.onStop() 81 | job?.apply { cancel() } 82 | } 83 | 84 | override fun getScreenTitle(): String { 85 | return "Exercise 10" 86 | } 87 | 88 | fun onFactorialComputed(result: BigInteger) { 89 | txtResult.text = result.toString() 90 | btnStartWork.isEnabled = true 91 | } 92 | 93 | fun onFactorialComputationTimedOut() { 94 | txtResult.text = "Computation timed out" 95 | btnStartWork.isEnabled = true 96 | } 97 | 98 | private fun getTimeout() : Int { 99 | var timeout: Int 100 | if (edtTimeout.text.toString().isEmpty()) { 101 | timeout = MAX_TIMEOUT_MS 102 | } else { 103 | timeout = Integer.valueOf(edtTimeout.text.toString()) 104 | if (timeout > MAX_TIMEOUT_MS) { 105 | timeout = MAX_TIMEOUT_MS 106 | } 107 | } 108 | return timeout 109 | } 110 | 111 | companion object { 112 | fun newInstance(): Fragment { 113 | return SolutionExercise10Fragment() 114 | } 115 | private const val MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise2/SolutionExercise2Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise2; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.techyourchance.multithreading.R; 10 | import com.techyourchance.multithreading.common.BaseFragment; 11 | 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | 14 | import androidx.annotation.NonNull; 15 | import androidx.annotation.Nullable; 16 | import androidx.fragment.app.Fragment; 17 | 18 | public class SolutionExercise2Fragment extends BaseFragment { 19 | 20 | public static Fragment newInstance() { 21 | return new SolutionExercise2Fragment(); 22 | } 23 | 24 | private byte[] mDummyData; 25 | private final AtomicBoolean mCountAbort = new AtomicBoolean(false); 26 | 27 | @Nullable 28 | @Override 29 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 30 | mDummyData = new byte[50 * 1000 * 1000]; 31 | return inflater.inflate(R.layout.fragment_exercise_2, container, false); 32 | } 33 | 34 | @Override 35 | public void onStart() { 36 | super.onStart(); 37 | countScreenTime(); 38 | } 39 | 40 | @Override 41 | public void onStop() { 42 | super.onStop(); 43 | mCountAbort.set(true); 44 | } 45 | 46 | @Override 47 | protected String getScreenTitle() { 48 | return "Exercise 2"; 49 | } 50 | 51 | private void countScreenTime() { 52 | 53 | mCountAbort.set(false); 54 | 55 | new Thread(new Runnable() { 56 | @Override 57 | public void run() { 58 | int screenTimeSeconds = 0; 59 | while (true) { 60 | try { 61 | Thread.sleep(1000); 62 | } catch (InterruptedException e) { 63 | return; 64 | } 65 | if (mCountAbort.get()) { 66 | return; 67 | } 68 | screenTimeSeconds++; 69 | Log.d("Exercise 2", "screen time: " + screenTimeSeconds + "s"); 70 | } 71 | } 72 | }).start(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise3/SolutionExercise3Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise3; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | import android.widget.TextView; 11 | 12 | import com.techyourchance.multithreading.R; 13 | import com.techyourchance.multithreading.common.BaseFragment; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.Nullable; 17 | import androidx.fragment.app.Fragment; 18 | 19 | public class SolutionExercise3Fragment extends BaseFragment { 20 | 21 | private static final int SECONDS_TO_COUNT = 3; 22 | 23 | public static Fragment newInstance() { 24 | return new SolutionExercise3Fragment(); 25 | } 26 | 27 | private Button mBtnCountSeconds; 28 | private TextView mTxtCount; 29 | 30 | private final Handler mUiHandler = new Handler(Looper.getMainLooper()); 31 | 32 | @Nullable 33 | @Override 34 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 35 | View view = inflater.inflate(R.layout.fragment_exercise_3, container, false); 36 | 37 | mBtnCountSeconds = view.findViewById(R.id.btn_count_seconds); 38 | mTxtCount = view.findViewById(R.id.txt_count); 39 | 40 | mBtnCountSeconds.setOnClickListener(new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | countIterations(); 44 | } 45 | }); 46 | 47 | return view; 48 | } 49 | 50 | @Override 51 | protected String getScreenTitle() { 52 | return "Exercise 3"; 53 | } 54 | 55 | private void countIterations() { 56 | mBtnCountSeconds.setEnabled(false); 57 | new Thread(new Runnable() { 58 | @Override 59 | public void run() { 60 | for (int i = 1; i <= SECONDS_TO_COUNT; i++) { 61 | final int count = i; 62 | mUiHandler.post(new Runnable() { 63 | @Override 64 | public void run() { 65 | mTxtCount.setText(String.valueOf(count)); 66 | } 67 | }); 68 | try { 69 | Thread.sleep(1000); 70 | } catch (InterruptedException e) { 71 | return; 72 | } 73 | } 74 | mUiHandler.post(new Runnable() { 75 | @Override 76 | public void run() { 77 | mTxtCount.setText("Done!"); 78 | mBtnCountSeconds.setEnabled(true); 79 | } 80 | }); 81 | } 82 | }).start(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise6/SolutionExercise6Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise6; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | 17 | import java.math.BigInteger; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | 23 | public class SolutionExercise6Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener { 24 | 25 | public static Fragment newInstance() { 26 | return new SolutionExercise6Fragment(); 27 | } 28 | 29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 30 | 31 | private EditText mEdtArgument; 32 | private EditText mEdtTimeout; 33 | private Button mBtnStartWork; 34 | private TextView mTxtResult; 35 | 36 | private ComputeFactorialUseCase mComputeFactorialUseCase; 37 | 38 | @Override 39 | public void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | mComputeFactorialUseCase = new ComputeFactorialUseCase(); 42 | } 43 | 44 | @Nullable 45 | @Override 46 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View view = inflater.inflate(R.layout.fragment_exercise_6, container, false); 48 | 49 | mEdtArgument = view.findViewById(R.id.edt_argument); 50 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 51 | mBtnStartWork = view.findViewById(R.id.btn_compute); 52 | mTxtResult = view.findViewById(R.id.txt_result); 53 | 54 | mBtnStartWork.setOnClickListener(v -> { 55 | if (mEdtArgument.getText().toString().isEmpty()) { 56 | return; 57 | } 58 | 59 | mTxtResult.setText(""); 60 | mBtnStartWork.setEnabled(false); 61 | 62 | 63 | InputMethodManager imm = 64 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 65 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 66 | 67 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 68 | 69 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()); 70 | }); 71 | 72 | return view; 73 | } 74 | 75 | @Override 76 | public void onStart() { 77 | super.onStart(); 78 | mComputeFactorialUseCase.registerListener(this); 79 | } 80 | 81 | @Override 82 | public void onStop() { 83 | super.onStop(); 84 | mComputeFactorialUseCase.unregisterListener(this); 85 | 86 | } 87 | 88 | @Override 89 | protected String getScreenTitle() { 90 | return "Exercise 6"; 91 | } 92 | 93 | private int getTimeout() { 94 | int timeout; 95 | if (mEdtTimeout.getText().toString().isEmpty()) { 96 | timeout = MAX_TIMEOUT_MS; 97 | } else { 98 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 99 | if (timeout > MAX_TIMEOUT_MS) { 100 | timeout = MAX_TIMEOUT_MS; 101 | } 102 | } 103 | return timeout; 104 | } 105 | 106 | @Override 107 | public void onFactorialComputed(BigInteger result) { 108 | mTxtResult.setText(result.toString()); 109 | mBtnStartWork.setEnabled(true); 110 | } 111 | 112 | @Override 113 | public void onFactorialComputationTimedOut() { 114 | mTxtResult.setText("Computation timed out"); 115 | mBtnStartWork.setEnabled(true); 116 | } 117 | 118 | @Override 119 | public void onFactorialComputationAborted() { 120 | mTxtResult.setText("Computation aborted"); 121 | mBtnStartWork.setEnabled(true); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise7/SolutionExercise7Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise7; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | 17 | import java.math.BigInteger; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | 23 | public class SolutionExercise7Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener { 24 | 25 | public static Fragment newInstance() { 26 | return new SolutionExercise7Fragment(); 27 | } 28 | 29 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 30 | 31 | private EditText mEdtArgument; 32 | private EditText mEdtTimeout; 33 | private Button mBtnStartWork; 34 | private TextView mTxtResult; 35 | 36 | private ComputeFactorialUseCase mComputeFactorialUseCase; 37 | 38 | @Override 39 | public void onCreate(@Nullable Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | mComputeFactorialUseCase = new ComputeFactorialUseCase( 42 | getCompositionRoot().getUiHandler(), 43 | getCompositionRoot().getThreadPool() 44 | ); 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 50 | View view = inflater.inflate(R.layout.fragment_exercise_7, container, false); 51 | 52 | mEdtArgument = view.findViewById(R.id.edt_argument); 53 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 54 | mBtnStartWork = view.findViewById(R.id.btn_compute); 55 | mTxtResult = view.findViewById(R.id.txt_result); 56 | 57 | mBtnStartWork.setOnClickListener(v -> { 58 | if (mEdtArgument.getText().toString().isEmpty()) { 59 | return; 60 | } 61 | 62 | mTxtResult.setText(""); 63 | mBtnStartWork.setEnabled(false); 64 | 65 | 66 | InputMethodManager imm = 67 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 68 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 69 | 70 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 71 | 72 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()); 73 | }); 74 | 75 | return view; 76 | } 77 | 78 | @Override 79 | public void onStart() { 80 | super.onStart(); 81 | mComputeFactorialUseCase.registerListener(this); 82 | } 83 | 84 | @Override 85 | public void onStop() { 86 | super.onStop(); 87 | mComputeFactorialUseCase.unregisterListener(this); 88 | 89 | } 90 | 91 | @Override 92 | protected String getScreenTitle() { 93 | return "Exercise 7"; 94 | } 95 | 96 | private int getTimeout() { 97 | int timeout; 98 | if (mEdtTimeout.getText().toString().isEmpty()) { 99 | timeout = MAX_TIMEOUT_MS; 100 | } else { 101 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 102 | if (timeout > MAX_TIMEOUT_MS) { 103 | timeout = MAX_TIMEOUT_MS; 104 | } 105 | } 106 | return timeout; 107 | } 108 | 109 | @Override 110 | public void onFactorialComputed(BigInteger result) { 111 | mTxtResult.setText(result.toString()); 112 | mBtnStartWork.setEnabled(true); 113 | } 114 | 115 | @Override 116 | public void onFactorialComputationTimedOut() { 117 | mTxtResult.setText("Computation timed out"); 118 | mBtnStartWork.setEnabled(true); 119 | } 120 | 121 | @Override 122 | public void onFactorialComputationAborted() { 123 | mTxtResult.setText("Computation aborted"); 124 | mBtnStartWork.setEnabled(true); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise8/SolutionExercise8Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise8; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | import com.techyourchance.threadposter.BackgroundThreadPoster; 17 | import com.techyourchance.threadposter.UiThreadPoster; 18 | 19 | import java.math.BigInteger; 20 | 21 | import androidx.annotation.NonNull; 22 | import androidx.annotation.Nullable; 23 | import androidx.fragment.app.Fragment; 24 | 25 | public class SolutionExercise8Fragment extends BaseFragment implements ComputeFactorialUseCase.Listener { 26 | 27 | public static Fragment newInstance() { 28 | return new SolutionExercise8Fragment(); 29 | } 30 | 31 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 32 | 33 | private EditText mEdtArgument; 34 | private EditText mEdtTimeout; 35 | private Button mBtnStartWork; 36 | private TextView mTxtResult; 37 | 38 | private ComputeFactorialUseCase mComputeFactorialUseCase; 39 | 40 | @Override 41 | public void onCreate(@Nullable Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | mComputeFactorialUseCase = new ComputeFactorialUseCase( 44 | new UiThreadPoster(), // you need to get this dependency from composition root 45 | new BackgroundThreadPoster() // you need to get this dependency from composition root 46 | ); 47 | } 48 | 49 | @Nullable 50 | @Override 51 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 52 | View view = inflater.inflate(R.layout.fragment_exercise_8, container, false); 53 | 54 | mEdtArgument = view.findViewById(R.id.edt_argument); 55 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 56 | mBtnStartWork = view.findViewById(R.id.btn_compute); 57 | mTxtResult = view.findViewById(R.id.txt_result); 58 | 59 | mBtnStartWork.setOnClickListener(v -> { 60 | if (mEdtArgument.getText().toString().isEmpty()) { 61 | return; 62 | } 63 | 64 | mTxtResult.setText(""); 65 | mBtnStartWork.setEnabled(false); 66 | 67 | 68 | InputMethodManager imm = 69 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 70 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 71 | 72 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 73 | 74 | mComputeFactorialUseCase.computeFactorialAndNotify(argument, getTimeout()); 75 | }); 76 | 77 | return view; 78 | } 79 | 80 | @Override 81 | public void onStart() { 82 | super.onStart(); 83 | mComputeFactorialUseCase.registerListener(this); 84 | } 85 | 86 | @Override 87 | public void onStop() { 88 | super.onStop(); 89 | mComputeFactorialUseCase.unregisterListener(this); 90 | 91 | } 92 | 93 | @Override 94 | protected String getScreenTitle() { 95 | return "Exercise 8"; 96 | } 97 | 98 | private int getTimeout() { 99 | int timeout; 100 | if (mEdtTimeout.getText().toString().isEmpty()) { 101 | timeout = MAX_TIMEOUT_MS; 102 | } else { 103 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 104 | if (timeout > MAX_TIMEOUT_MS) { 105 | timeout = MAX_TIMEOUT_MS; 106 | } 107 | } 108 | return timeout; 109 | } 110 | 111 | @Override 112 | public void onFactorialComputed(BigInteger result) { 113 | mTxtResult.setText(result.toString()); 114 | mBtnStartWork.setEnabled(true); 115 | } 116 | 117 | @Override 118 | public void onFactorialComputationTimedOut() { 119 | mTxtResult.setText("Computation timed out"); 120 | mBtnStartWork.setEnabled(true); 121 | } 122 | 123 | @Override 124 | public void onFactorialComputationAborted() { 125 | mTxtResult.setText("Computation aborted"); 126 | mBtnStartWork.setEnabled(true); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise9/ComputeFactorialUseCase.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise9; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import com.techyourchance.multithreading.common.BaseObservable; 7 | 8 | import java.math.BigInteger; 9 | 10 | import androidx.annotation.WorkerThread; 11 | import io.reactivex.Flowable; 12 | import io.reactivex.Observable; 13 | import io.reactivex.schedulers.Schedulers; 14 | 15 | public class ComputeFactorialUseCase { 16 | 17 | public static class Result { 18 | private final boolean mIsAborted; 19 | private final boolean mIsTimedOut; 20 | private final BigInteger mResult; 21 | 22 | public Result(boolean isAborted, boolean isTimedOut, BigInteger result) { 23 | mIsAborted = isAborted; 24 | mIsTimedOut = isTimedOut; 25 | mResult = result; 26 | } 27 | 28 | public boolean isAborted() { 29 | return mIsAborted; 30 | } 31 | 32 | public boolean isTimedOut() { 33 | return mIsTimedOut; 34 | } 35 | 36 | public BigInteger getResult() { 37 | return mResult; 38 | } 39 | } 40 | 41 | private int mNumberOfThreads; 42 | private ComputationRange[] mThreadsComputationRanges; 43 | 44 | private long mComputationTimeoutTime; 45 | 46 | public Observable computeFactorial(final int argument, final int timeout) { 47 | 48 | initComputationParams(argument, timeout); 49 | 50 | return Flowable 51 | .range(0, mNumberOfThreads) 52 | .parallel(mNumberOfThreads) 53 | .runOn(Schedulers.io()) 54 | .map(this::computePart) 55 | .sequential() 56 | .scan((bigInteger, bigInteger2) -> { 57 | if (isTimedOut()) { 58 | return bigInteger; 59 | } else { 60 | return bigInteger.multiply(bigInteger2); 61 | } 62 | }) 63 | .last(new BigInteger("0")) 64 | .map(result -> { 65 | if (isTimedOut()) { 66 | return new Result(false, true, result); 67 | } 68 | return new Result(false, false, result); 69 | }) 70 | .onErrorReturnItem(new Result(true, false, new BigInteger("0"))) 71 | .toObservable(); 72 | } 73 | 74 | @WorkerThread 75 | private BigInteger computePart(int id) { 76 | long rangeStart = mThreadsComputationRanges[id].start; 77 | long rangeEnd = mThreadsComputationRanges[id].end; 78 | BigInteger product = new BigInteger("1"); 79 | for (long num = rangeStart; num <= rangeEnd; num++) { 80 | if (isTimedOut()) { 81 | break; 82 | } 83 | product = product.multiply(new BigInteger(String.valueOf(num))); 84 | } 85 | return product; 86 | } 87 | 88 | private void initComputationParams(int factorialArgument, int timeout) { 89 | mNumberOfThreads = factorialArgument < 20 90 | ? 1 : Runtime.getRuntime().availableProcessors(); 91 | 92 | mThreadsComputationRanges = new ComputationRange[mNumberOfThreads]; 93 | 94 | initThreadsComputationRanges(factorialArgument); 95 | 96 | mComputationTimeoutTime = System.currentTimeMillis() + timeout; 97 | } 98 | 99 | private void initThreadsComputationRanges(int factorialArgument) { 100 | int computationRangeSize = factorialArgument / mNumberOfThreads; 101 | 102 | long nextComputationRangeEnd = factorialArgument; 103 | for (int i = mNumberOfThreads - 1; i >= 0; i--) { 104 | mThreadsComputationRanges[i] = new ComputationRange( 105 | nextComputationRangeEnd - computationRangeSize + 1, 106 | nextComputationRangeEnd 107 | ); 108 | nextComputationRangeEnd = mThreadsComputationRanges[i].start - 1; 109 | } 110 | 111 | // add potentially "remaining" values to first thread's range 112 | mThreadsComputationRanges[0].start = 1; 113 | } 114 | 115 | private long getRemainingMillisToTimeout() { 116 | return mComputationTimeoutTime - System.currentTimeMillis(); 117 | } 118 | 119 | private boolean isTimedOut() { 120 | return System.currentTimeMillis() >= mComputationTimeoutTime; 121 | } 122 | 123 | private static class ComputationRange { 124 | private long start; 125 | private long end; 126 | 127 | public ComputationRange(long start, long end) { 128 | this.start = start; 129 | this.end = end; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/java/com/techyourchance/multithreading/solutions/exercise9/SolutionExercise9Fragment.java: -------------------------------------------------------------------------------- 1 | package com.techyourchance.multithreading.solutions.exercise9; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.inputmethod.InputMethodManager; 9 | import android.widget.Button; 10 | import android.widget.EditText; 11 | import android.widget.TextView; 12 | 13 | import com.techyourchance.multithreading.DefaultConfiguration; 14 | import com.techyourchance.multithreading.R; 15 | import com.techyourchance.multithreading.common.BaseFragment; 16 | 17 | import java.math.BigInteger; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | import androidx.fragment.app.Fragment; 22 | import io.reactivex.android.schedulers.AndroidSchedulers; 23 | import io.reactivex.disposables.Disposable; 24 | import io.reactivex.schedulers.Schedulers; 25 | 26 | public class SolutionExercise9Fragment extends BaseFragment { 27 | 28 | public static Fragment newInstance() { 29 | return new SolutionExercise9Fragment(); 30 | } 31 | 32 | private static int MAX_TIMEOUT_MS = DefaultConfiguration.DEFAULT_FACTORIAL_TIMEOUT_MS; 33 | 34 | private EditText mEdtArgument; 35 | private EditText mEdtTimeout; 36 | private Button mBtnStartWork; 37 | private TextView mTxtResult; 38 | 39 | private ComputeFactorialUseCase mComputeFactorialUseCase; 40 | 41 | private @Nullable Disposable mDisposable; 42 | 43 | @Override 44 | public void onCreate(@Nullable Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | mComputeFactorialUseCase = new ComputeFactorialUseCase(); 47 | } 48 | 49 | @Nullable 50 | @Override 51 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 52 | View view = inflater.inflate(R.layout.fragment_exercise_9, container, false); 53 | 54 | mEdtArgument = view.findViewById(R.id.edt_argument); 55 | mEdtTimeout = view.findViewById(R.id.edt_timeout); 56 | mBtnStartWork = view.findViewById(R.id.btn_compute); 57 | mTxtResult = view.findViewById(R.id.txt_result); 58 | 59 | mBtnStartWork.setOnClickListener(v -> { 60 | if (mEdtArgument.getText().toString().isEmpty()) { 61 | return; 62 | } 63 | 64 | mTxtResult.setText(""); 65 | mBtnStartWork.setEnabled(false); 66 | 67 | InputMethodManager imm = 68 | (InputMethodManager) requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE); 69 | imm.hideSoftInputFromWindow(mBtnStartWork.getWindowToken(), 0); 70 | 71 | int argument = Integer.valueOf(mEdtArgument.getText().toString()); 72 | 73 | mDisposable = mComputeFactorialUseCase.computeFactorial(argument, getTimeout()) 74 | .subscribeOn(Schedulers.io()) 75 | .observeOn(AndroidSchedulers.mainThread()) 76 | .subscribe(result -> { 77 | if (result.isAborted()) { 78 | onFactorialComputationAborted(); 79 | } else if (result.isTimedOut()) { 80 | onFactorialComputationTimedOut(); 81 | } else { 82 | onFactorialComputed(result.getResult()); 83 | } 84 | }); 85 | }); 86 | 87 | return view; 88 | } 89 | 90 | @Override 91 | public void onStop() { 92 | super.onStop(); 93 | if (mDisposable != null) { 94 | mDisposable.dispose(); 95 | } 96 | } 97 | 98 | @Override 99 | protected String getScreenTitle() { 100 | return "Exercise 9"; 101 | } 102 | 103 | private int getTimeout() { 104 | int timeout; 105 | if (mEdtTimeout.getText().toString().isEmpty()) { 106 | timeout = MAX_TIMEOUT_MS; 107 | } else { 108 | timeout = Integer.valueOf(mEdtTimeout.getText().toString()); 109 | if (timeout > MAX_TIMEOUT_MS) { 110 | timeout = MAX_TIMEOUT_MS; 111 | } 112 | } 113 | return timeout; 114 | } 115 | 116 | public void onFactorialComputed(BigInteger result) { 117 | mTxtResult.setText(result.toString()); 118 | mBtnStartWork.setEnabled(true); 119 | } 120 | 121 | public void onFactorialComputationTimedOut() { 122 | mTxtResult.setText("Computation timed out"); 123 | mBtnStartWork.setEnabled(true); 124 | } 125 | 126 | public void onFactorialComputationAborted() { 127 | mTxtResult.setText("Computation aborted"); 128 | mBtnStartWork.setEnabled(true); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-hdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-mdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-xhdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-xxhdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techyourchance/android-multithreading-masterclass/d68cb3839a6dd15b1a8d361de40be8dcff203686/app/src/main/res/drawable-xxxhdpi/ic_arrow_back.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | 24 | 25 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_atomicity_demonstration.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 |