Race Conditions and Synchronization - Object Oriented Programming and Data Structures - Lecture Slides, Slides of Object Oriented Programming

Lecture from Object Oriented Programming and Data Structures course with following key points: Race Conditions and Synchronization, Java Synchronization, Visualizing Deadlock, Higher Level Abstractions, Producer, Bounded Buffer, Visualizing, Debugging Concurrent Code

Typology: Slides

2013/2014

Uploaded on 01/29/2014

sundar
sundar 🇮🇳

4.7

(9)

104 documents

1 / 33

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Race Conditions and
Synchronization
docsity.com
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21

Partial preview of the text

Download Race Conditions and Synchronization - Object Oriented Programming and Data Structures - Lecture Slides and more Slides Object Oriented Programming in PDF only on Docsity!

Race Conditions and

Synchronization

Reminder

  • A “race condition” arises if two threads try and share some data
  • One updates it and the other reads it, or both update the data
  • In such cases it is possible that we could see the data “in the middle” of

being updated

  • A “race condition”: correctness depends on the update racing to completion

without the reader managing to glimpse the in-progress update

  • Synchronization (aka mutual exclusion) solves this

Java Synchronization (Locking)

public void doSomething() { synchronized (this) { ... } } public synchronized void doSomething() { ... }

• You can lock on any object, including this

is equivalent to

How locking works

  • Only one thread can “hold” a lock at a time
    • If several request the same lock, Java somehow decides which will get it
  • The lock is released when the thread leaves the synchronization block
    • synchronized(someObject) { protected code }
    • The protected code has a mutual exclusion guarantee: At most one thread can

be in it

  • When released, some other thread can acquire the lock

Visualizing deadlock

Process A Process B X Y

A has a lock on X

wants a lock on Y

B has a lock on Y

wants a lock on X

Deadlocks always involve cycles

  • They can include 2 or more threads or processes in a waiting cycle
  • Other properties:
    • The locks need to be mutually exclusive (no sharing of the objects being

locked)

  • The application won’t give up and go away (no timer associated with the lock

request)

  • There are no mechanisms for one thread to take locked resources away from

another

thread – no “preemption”

“... drop that mouse or you’ll be down to 8 lives”

Higher level abstractions

  • Locking is a very low-level way to deal with synchronization
    • Very nuts-and-bolts
  • So many programmers work with higher level concepts. Sort of like

ADTs for synchronization

  • We’ll just look at one example today
  • There are many others; take cs4410 to learn more

A producer/consumer example

  • Thread A produces loaves of bread and puts them on a shelf with

capacity K

  • For example, maybe K=
  • Thread B consumes the loaves by taking them off the shelf
  • Thread A doesn’t want to overload the shelf
  • Thread B doesn’t wait to leave with empty arms producer shelves consumer

Things to notice

  • Wait needs to wait on the same object that you used for

synchronizing (in our example, “this”, which is this instance of the

Bakery)

  • Notify wakes up just one waiting thread, notifyall wakes all of them

up

  • We used a while loop because we can’t predict exactly which thread

will wake up “next”

Bounded Buffer

  • Here we take our producer/consumer and add a notion of passing

something from the producer to the consumer

  • For example, producer generates strings
  • Consumer takes those and puts them into a file
  • Question: why would we do this?
  • Keeps the computer more steadily busy

Bounded Buffer example

class BoundedBuffer { int putPtr = 0, getPtr = 0; // Next slot to use int available = 0; // Items currently available final int K = 10; // buffer capacity T[] buffer = new T[K]; public synchronized void produce(T item) { while(available == K) this.wait(); // Wait until not full buffer[putPtr++ % K] = item; ++available; this.notifyall(); // Signal: not empty } public synchronized T consume() { while(available == 0) this.wait(); // Wait until not empty --available; T item = buffer[getPtr++ % K]; this.notifyall(); // Signal: not full return item; } }

In an ideal world…

  • Bounded buffer allows producer and consumer to both run

concurrently, with neither blocking

  • This happens if they run at the same average rate
  • … and if the buffer is big enough to mask any brief rate surges by either of

the two

  • But if one does get ahead of the other, it waits
    • This avoids the risk of producing so many items that we run out of computer

memory for them. Or of accidentally trying to consume a non-existent item.

Code we’re given is unsafe

class BST { Object name; // Name of this node Object value; // Value of associated with that name BST left, right; // Children of this node // Constructor public void BST(Object who, Object what) { name = who; value = what; } // Returns value if found, else null public Object get(Object goal) { if(name.equals(goal)) return value; if(name.compareTo(goal) < 0) return left==null? null: left.get(goal); return right==null? null: right.get(goal); } // Updates value if name is already in the tree, else adds new BST node public void put(Object goal, object value) { if(name.equals(goal)) { this.value = value; return; } if(name.compareTo(goal) < 0) { if(left == null) { left = new BST(goal, value); return; } left.put(goal, value); } else { if(right == null) { right = new BST(goal, value); return; } right.put(goal, value); } } }

Attempt

  • Just make both put and get synchronized:
    • public synchronized Object get(…) { … }
    • public synchronized void put(…) { … }
  • Let’s have a look….