UNC COMP 301 - F26/Lec 14 - Race conditions, Synchronized methods; Observer Design Pattern
Watch on YouTube →
Overview
The lecture concludes Java concurrency with thread scheduling, race conditions, synchronization, locks, and deadlocks, then introduces the Observer design pattern through a basketball-game example. The code examples show how shared counters can produce inconsistent results, how synchronized access protects them, and how a game subject registers and notifies fan observers when scores change.
Key takeaways
- A shared counter's increment is not a single indivisible action: competing threads can read the same value and overwrite each other's updates, so 100,000 increments and decrements may not net to zero.
- Java synchronized methods on the same object serialize access to that object's protected methods, preventing the counter example's lost updates.
- When using an explicit Lock, put unlock() in a finally block so exceptions cannot leave the lock held and block other threads indefinitely.
- Deadlock can arise from opposing lock order: one thread holds A and waits for B while another holds B and waits for A.
- The Observer pattern separates a subject's state changes from observer responses: observers register, the subject calls update() on each observer after an event, and observers may unregister.
- In the game example, scorePoints() changes the score and notifies both fans, but each fan cheers only when its own team is winning.
Chapters
0:00
Runnable Tasks, Lambdas, Threads, and join()
- A Runnable has one abstract method, run(), so Java lambda syntax can supply its implementation.
- Calling task.run() executes synchronously; creating and starting Thread objects runs the task asynchronously.
- The example starts two threads on the same task, then uses join() so the main thread waits for both before printing completion.
4:40
Why Thread.join() Can Throw InterruptedException
- Thread.join() makes the calling thread wait, so Java declares the checked InterruptedException.
- A different thread can call interrupt() on the waiting thread; code must catch the exception or declare it in its own method signature.
- The lecture frames interruption as a way to respond if a waiting thread needs to stop rather than remain blocked.
8:46
Operating-System Scheduling, Cores, and Context Switching
- The parallel demo increases its thread count while generating 100 million random numbers; on the instructor's eight-core computer, execution time generally fell through eight threads.
- Beyond the available cores, multiple runnable threads share core time, adding context-switching overhead.
- The operating-system scheduler assigns threads to cores and uses load balancing; a thread may resume on a different core after being time-sliced.
13:58
Race Conditions Require Shared State and Concurrent Access
- A race condition can occur when multiple threads access shared heap memory and at least one thread writes to the shared object.
- Thread stacks are private to each thread, while objects on the heap can be shared.
- The result can depend on the timing and interleaving of concurrent reads and writes.
16:10
Counter Example: Lost Updates from Increment and Decrement
- A shared Counter starts at zero; one thread increments it 100,000 times while another decrements it 100,000 times.
- Although the expected final value is zero, repeated runs produce varying nonzero results, including negative 5,566 and positive 17,000.
- An increment is a read-modify-write sequence: read the current value, calculate a temporary result, then write it back.
- If both threads read the same old value, one thread can overwrite the other's update; the final result depends on which write happens last.
26:56
Synchronized Methods Protect the Shared Counter
- Adding synchronized to addOne() and subtractOne() prevents those methods from executing concurrently on the same Counter instance.
- The synchronization makes each method's read-modify-write sequence effectively indivisible relative to competing synchronized methods on that object.
- The example then consistently produces zero after both threads finish; whether to synchronize getValue() depends on its broader access context.
29:18
Locks, try-finally, and Reentrant Access
- The lecture illustrates synchronization with a Lock reference implemented by ReentrantLock, using lock() before the protected code and unlock() afterward.
- Threads contending for the same lock on the same object wait until the current holder releases it; separate lock instances protect separate resources.
- A try-finally block ensures unlock() runs even if the protected code throws an exception, preventing other threads from waiting indefinitely.
- A reentrant lock allows the same thread to acquire a lock it already holds, avoiding self-blocking in nested calls.
37:00
Deadlock from Opposing Lock Acquisition
- Deadlock can occur when one thread holds object A's lock while waiting for object B, and another holds B's lock while waiting for A.
- The threads then wait indefinitely because neither can acquire the lock needed to continue and release its current lock.
- The lecture emphasizes designing lock usage carefully; rare timing-dependent failures can be difficult to reproduce and debug.
44:00
Observer Pattern: Subjects, Observers, and Events
- The Observer pattern is behavioral: a subject tracks observers and notifies them when an event changes the subject's state.
- In the basketball example, a game is the subject, fans are observers, and a team scoring is an event.
- Other examples include YouTube channel subscribers receiving upload notifications and sensors triggering an air conditioner or alarm.
- A subject can have multiple observers and events; examples include fouls, substitutions, halftime, and the end of a game.
53:36
Observer Registration, Notification, and Pattern Terminology
- An observer registers by calling the subject's addObserver() method and passing this; the subject stores that reference for later notifications.
- When an event occurs, the subject calls update() on each registered observer; observers can also unregister when they no longer want updates.
- Common terminology includes listener for observer, observable or dispatcher for subject, and notify/update or dispatch for notification.
57:36
Subject Implementation: Store and Update Observers
- A subject maintains a collection such as a List<Observer> and provides methods to add and remove observer references.
- Its notification method iterates through the list and calls update() on every observer.
- The subject should notify observers whenever a relevant event changes its state.
1:00:00
Game and Fan Interfaces Define the Observer Contract
- The example defines a Game subject interface with addObserver(), removeObserver(), and notifyObservers() methods.
- Game-specific methods track team scores, award points, print the score, and identify the team currently winning.
- The Fan observer interface needs only update(), making it a single-abstract-method interface that can support lambda expressions when its implementation needs no fields.
1:03:00
Basketball Game State and Team-Specific Fan Observers
- The game implementation stores observer references, home and visiting team names, and both teams' scores, initialized to zero.
- scorePoints() updates the appropriate team's score and then notifies observers.
- The UNCC fan and Duke fan implementations retain a game reference and cheer when their respective team is winning.
- These fan classes use fields and constructors, so the illustrated implementations cannot be replaced directly by fieldless lambda expressions.
1:08:00
Observer Demo Output and UML Relationships
- The main method creates a game and two fans, registers them through their constructors, and then awards points to Duke and UNCC.
- Only the fan whose team is winning responds to each score update.
- The UML diagram shows GameImplementation implementing the Game interface and UNCCFan and DukeFan implementing the Fan interface with dashed realization arrows.
- GameImplementation aggregates zero or more independently created Fan objects; a fan's reference to the Game interface is shown as an association.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, Muhammad Sayeed Ghani.