This project is configured to use a local Gradle installation. If you're opening this project on your own system, please make sure to:
-
Go to Settings/Preferences > Build, Execution, Deployment > Build Tools > Gradle.
-
Under Gradle settings, change the Gradle distribution to:
-
Use local Gradle distribution, and
-
Set the Gradle home path to your own local Gradle installation directory.
If you don’t have Gradle installed locally, you can either:
-
Install Gradle manually and configure the path, or
-
Change the setting to Use Gradle wrapper instead.
-
Understand the concept of race conditions in multithreaded programming.
- A race condition occurs when multiple threads try to access and modify shared data concurrently, and the final outcome depends on the timing of thread execution.
-
Identify the problem of shared mutable state (
counterin this case) being updated by multiple threads without synchronization.
-
Understand the purpose and usage of
LockandReentrantLockfor thread synchronization. -
Identify and implement a critical section using explicit locking.
-
Observe thread behavior when competing for a shared resource.
-
understand how to pass parameters to threads using custom
Runnableimplementations. -
Understand the concept of a fair lock using
ReentrantLock(true)and its scheduling implications. -
Learn how
ReentrantLockallows the same thread to acquire the lock multiple times (reentrancy). -
Use and differentiate between
lock()andtryLock()with and without a timeout. -
Practice inspecting lock state using methods like
isLocked(),getHoldCount(), andisHeldByCurrentThread().
-
Understand the concept of deadlock in multithreaded programming.
-
Learn how holding one lock and waiting for another can result in a deadlock situation.
-
Explore how the order of acquiring locks can lead to contention and deadlock.
-
Understand the concept of synchronization in multithreading:
- The
synchronizedkeyword is used to prevent multiple threads from accessing the same critical section of code at the same time, which ensures thread safety and prevents race conditions.
- The
-
Learn how to synchronize access to shared resources:
- The
PrintDemoclass is a shared resource between the two threads (ThreadDemo). The synchronization ensures that only one thread at a time can access theprintCount()method, which is the critical section in this example.
- The
-
Understand the difference between synchronized methods and synchronized blocks:
- Synchronized methods, like
setObject()andgetObject(), ensure that only one thread can execute the method at a time on a given instance of the class. In contrast, synchronized blocks, like insetObject2()andgetObject2(), allow fine-grained control over synchronization by locking only specific sections of the method (in this case, the block that manipulates theobject).
- Synchronized methods, like
-
Learn how to synchronize instance methods using
synchronizedkeyword:- The methods
setObject()andgetObject()are synchronized on the instance (this), ensuring that only one thread can access them at a time for a specific object.
- The methods
-
Examine how synchronization helps prevent race conditions and ensures thread safety:
- By synchronizing methods and blocks, we prevent race conditions where multiple threads might try to update shared resources (
objectorstaticObject) concurrently, which could lead to inconsistent results.
- By synchronizing methods and blocks, we prevent race conditions where multiple threads might try to update shared resources (
-
Understand the concept of using
synchronizedfor both instance-level and class-level resources:- Instance-level synchronization is applied using
this(the instance object), whereas class-level synchronization is applied using the class itself (TheClass.class).
- Instance-level synchronization is applied using
-
Understand the concept of semaphores and how they control access to shared resources:
- The example demonstrates how semaphores are used to limit the number of threads accessing a shared resource. In this case, the semaphore is initialized with 2 permits, allowing at most 2 threads to enter the critical section simultaneously.
-
Learn how to use
Semaphore.acquire()andSemaphore.release():-
acquire()is used to request a permit. If no permits are available, the calling thread will block until one becomes available. -
release()is used to release a permit, making it available for other threads.
-
-
Explore the role of semaphores in controlling concurrency and preventing resource contention:
- Semaphores are useful when you want to limit the number of threads accessing a specific resource (in this case, the simulated resource), preventing too many threads from causing contention or overload.
You should complete the following classes, located in Workshop folder.
- Prevent race condition from happening using
ReentrantLock - Run the program to confirm that the final value of
counteris exactly 2,000,000 (1,000,000 increments per thread).
- Prevent race condition from happening using
synchronized - Run the program to confirm that the final value of
counteris exactly 2,000,000 (1,000,000 increments per thread).
- Prevent deadlock (hint: if you make sure that all locks are always taken in the same order by any thread, deadlocks cannot occur in this example)
- Test that both threads run to completion without getting stuck (no deadlock).
- Search for the Taylor series of sin(x).
- Read this article to get to know BigDecimal in Java Mastering Big Decimals in Java
- Implement the
factorial(int n)method usingBigDecimal. - In the
run()method, calculate the n-th term of the Taylor series forsin(x): - Add the calculated term to the shared variable
sum. - Compare the final result with the accurate pre-calculated value of
sin(0.01). - Use
toPlainString()to printBigDecimalvalues without scientific notation.