700 likes | 862 Views
Concurrent access to Objects and variables. Concurrent access. With multi-threaded programming, threads can access same data, controls need to be in place to ensure that their access has the desired effects and the data remains consistent. Problems expected:
E N D
Concurrent access to Objects and variables Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Concurrent access • With multi-threaded programming, threads can access same data, controls need to be in place to ensure that their access has the desired effects and the data remains consistent. • Problems expected: • Current thread get preempted halfway its execution for other thread to execute. • While thread is preempted, other thread access same data (object’s instance variables) and performs changes in data that was or was being manipulated by preempted thread. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Designing solutions • Make operations atomic (to avoid race conditions: two threads sending same message to same object at the same time and behavior of code depends on who wins) • Do not allow a thread to be suspended in the middle of an operation while giving access to another thread to perform operations on same data. • Warning: these solutions create their own set of new problems. • Hard news: we need the solutions proposed but need to deal with the problems created by them. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Multi-threaded programming properties • Safety : nothing bad ever happens • Safety failure:Unintended behavior at run time • Desired goal: all object states are consistent. • Liveness: anything ever happens • Liveness failure: things just stop running • Desired goal: all threads have their chance to run to expected completion. • These properties need to be balanced • But: better do nothing (liveness failure) that something that leads to a safety failure • On the other hand: there are many situations where is better to give an incorrect answer than not one at all. • Warning: some of the easiest thing to improve liveness property can destroy safety property and viceversa. • Get them both right is the challenge in Multi-threaded programming. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Multi-threaded software properties • On top of these two fundamental multi-threaded programming properties, we have the following two software eng. Properties: • Performance The extend to which activities execute soon and quickly. • Reusability The utility of objects and classes across multiple contexts. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Programming gets harder • Most multi-threaded safety matters cannot be checked automatically • Must rely on the programmer and design being implemented: • Strong hint: design up-front. • Design vs. code ratio: 10 – 1 • There are many methodologies to prove manually that designs are safe. We will cover some during this course. • But most importantly: safety properties relies strongly on the software engineering practices by the programmers. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Maintaining object’s consistency • Sometime hard to completely establish what is legal and meaningful for a class • Better route: establish conceptual-level invariants. (Example: volume for water tank must be between zero and capacity) • Object’s consistency follows preservation of their invariants: an object is consistent if all fields obey their invariants. • To uncover invariants: • Clearly understand object’s properties • Constraints on object’s representation • Note: safe objects may occasionally enter transiently inconsistent states, in the mist of methods; but they never attempt to initialize new actions while inconsistent. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Importance of Object’s invariants • One reason for being more careful about invariants in multi-threaded programming: it is much easier to break them than in sequential programming. • Remainder: the need of protection against inconsistency also arises in sequential programming: • Processing exceptions • Making callbacks • Self-calls between methods in a class. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Maintaining consistency: Exclusion • Guaranteeing atomicity • Failures of maintaining atomicity: • Read/Write conflicts • Write/Write conflicts • Impossible to predict the consequences of actions when objects are inconsistent. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Liveness • Goal: every activity eventually progresses towards completion • But an activity may fail (even transiently) for any number of possibly interrelated reason Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Liveness failures • Locking • Waiting for a condition • Input from another device • CPU contention • Failure due to exception, error or fault. • But: • Temporal blocking of progress is expected, but this blocking time cannot be unbounded. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Potential permanent liveness failures • Deadlock : circular dependencies • Thread A hold a lock for object X, and needs to acquire and lock object Y • Thread B already locks object Y, and needs to acquire lock for object X. • Missed signals: thread started waiting after given notification to wake up was given. • Nested monitor lockouts: waiting thread holds lock needed by another thread attempting to wake it up. • Livelock: continuously retried action continuously fails. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Potential permanent liveness failures • Starvation • Resource exhaustion • Distributed failure: a remote server machine connected by a socket crashes or otherwise becomes inaccessible. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Performance • Throughput: number of ops/unit of time • Latency: time elapsed between issuing a message and its execution • Capacity: number of simultaneous activities • Efficiency: throughput/(#comp resources) • Scalability: rate at which latency and throughput improve when resources are added to system. • Degradation: rate at which latency or throughput worsens as more clients, activities, operations are added without adding resources. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Efficiency trade-off • Poorer efficiency for better latency, and scalability • Locks • Monitors • Threads • Context-switching • Scheduling • Locality • Algorithmics • All the above reduce efficiency due to overhead and contention that can slow down program. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Strategies for safety in objects • Immutability • Avoid state changes. • Synchronization • Dynamically ensuring exclusive access. • Containment • Structurally ensuring exclusive access. • Variants: • Stateless methods • Partial synchronization • Managed ownership Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Immutable Objects • To avoid synchronization, do not change the state of the objects. • Programs are easier to understand • But, these programs are unable to handle user interactions, cooperating threads, etc. • But we can at least search for: selected immutability. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Simplest immutable objects • Do not have state at all. • Their methods are stateless; they are given data through parameters. Such data is primitive type data or object reference which are not updated by the method. Class StatelessAdder { public int add(int a, int b) { return a + b;} } • Methods are always safe and live Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Simplest immutable objects • This safety and liveness also hold for classes with only final fields. class ImmutableAdder { public ImmutableAdder (int a) { offset = a;} public int addOffset(int b) { return offset + b; } private final int offset; } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Immutability in API’s • ADT’s implementing values: colors, strings. These classes are called Value containers. • Can create a numeric class where the methods never alter their fields but construct and return values as the result of the methods. • Design different classes supporting different usages: provide immutable and updateable versions: String, StringBuffer. • Protect objects via copying, when is rarely done and is cheap. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Immutability for sharing • Immutability is a useful technique for sharing values and avoid costs associated from exclusion in multi-threaded programming. (MTP) • Instances of many utility classes in MTP are intrinsically immutable and shared by many other objects. class Relay { public Relay ( Server s) { server = s;} public void execute() { server.execute();} } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Constructing immutable objects • Fields must be final • Do not allow fields to be accessed until construction is completed. (must lock) • Field values must be copied (or can use references if values are immutable themselves) • Constructors depending on the construction of other objects must relay on locking. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Immutability variant: stateless methods • A mutable object can provide services via strictly stateless methods, ie, have no bearing on the object’s state. • Stateless methods can be used as if they where methods of an immutable object. • Stateless methods often make copies of arguments and results. • Copying only works if it is to be used locally. • Java does not provide pass-by-copy parameter passing mechanism, so it is left up to the programmer. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Basic concepts: atomic operations (atomicity). • Atomic operations can't be interrupted (divided) • In java assigment to int, boolean is atomic • But assignment to double or long is not atomic long x; • thread 1 may attempt to execute: x = 0x0123456789abcdef • thread 2 may attempt to execute : x = 0; • possible results: 0x0123456789abcdef; 0x0123456700000000; 0x0000000089abcdef; 0x0000000000000000; Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
64-bit assignment • 64-bit assignment is effectively implemented as: x.high = 0x01234567 x.low = 0x89abcdef; • You can be preempted between the assignment operations, unless you lock. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Basic concepts: synchronization • Mechanism to provide locking or exclusion by other threads while a thread is executing some common code. • Mechanisms to assure that multiple threads: • Start execution at the same time and run concurrently ("condition variables" or "events"). • Do not run simultaneously when accessing the same object ("monitors"). • Do not run simultaneously when accessing the same code ("critical sections"). • The synchronizedkeyword is essential in implementing synchronization (but is poorly designed.) • No timeout, so deadlock detection is impossible. • There is no way to determine if an object is already lock due to synchronization • If an object attempts to lock an already lock object, it is placed in a list waiting to acquire the lock, but no thread can be removed from that list by client. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Example of need to exclude public class Even { /** * ensure: nVal() % 2 == 0 */ public void next() { ++n; ++n; } public int nVal(){ return n; } private int n = 0; } Typical behavior: This code might work well most of the time, and possibly only rarely will exhibit safety failure. When it does: hard to debug, as the inconsistency is usually manifested in other unrelated objectstate. Solution: Declare next() synchronized. This excludes access to this code by one thread when other thread is executing it: becomes atomic. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Locks and Objects • Every object instance has ONE LOCK and only one. • Scalar fields can only be locked via their enclosing object instance. • Methods and blocks of code can be marked as synchronized; fields are not. • Arrays are objects as is Class object. • Locking an array of objects does not automatically lock all its elements. • There are NO constructs for simultaneously locking multiple objects in a single atomic op. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Lock ownership • An object’s lock is held (owned) by the thread executing synchronized code of the object. • Thus, same thread can call other synchronized methods of the same object. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Block synchronization • A block of code can be synchronized, but it takes an argument on which object to block. Very commonly the object is this synchronized(obj) { //code to lock } Semantics: • No other thread can execute block concurrently. • No other synchronized method or block of code can be executed by any other thread on same object. • Block synchronization can be done on any object • Use it for protecting access to objects with no synchronized methods. • For synchronizing a series of accesses to an object. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Synchronization and inheritance • The synchronized keyword is not part of the method signature • It is NOT a method modifier • It is not inheritable • Methods in interfaces cannot be specified synchronized • Constructors can not be synchronized either, but block synchronization can be used within. • Synchronized instance methods in subclasses employ the same lock as those in the superclass. • Synchronizing an inner class method is independent of its outer class. • Inner classes can use a block with the instance of the outer lock as the object to lock. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Acquiring and releasing lock • Lock is acquired on entry to method or block. • Lock is released on exit of synchronized code, including exit due to an exception. • Locks are per-thread not per invocation of method. • A thread hitting synchronized accesses such code if: • No thread has the lock on the object for such code. OR • Same thread already possesses the lock of such object. • Therefore, a thread possessing an object’s lock can make recursive calls without being locked out. • a synchronized method or block obeys these rules only with respect to other synchronized methods or blocks on the same object. • Methods not synchronized still can execute at any time by any other thread even if synchronized code is executing. • Any thread locked out due to a synchronized code will be place on “hold in a queue” • When the object’s lock is released, there is no guarantee on which thread will acquire the lock next. No fairness, but indeterminancy. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Static fields or methods and synchronization • Object synchronization does not affect static class fields or methods • Static class fields or methods are synchronized with the lock of the Class object for that class. • When synchronizing a static method it acquires a different lock than the lock for any object of that class; but all static methods will use same lock. • A static block can be used against the lock of the object Class for that class. • Example: having class C • synchronized (C.class) {….} Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Fully synchronized objects • Safest (but might not be the best) multi-threaded strategy is to restrict attention to fully synchronized objects: • All public methods are synchronized • There are no public fields or other encapsulation violations • All methods terminate • All fields are initialized to a consistent state in the constructors • State of object is consistent (obey invariants) at both the beginning and end of each method, even in the presence of exceptions. • But this use costs and may lead to deadlocks. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Code execution exclusion via synchronization • With synchronized code declaration, two or more threads are guarantee not to interfere with each other on the same object. • If an object has synchronized methods and two threads invoke such methods simultaneously on the same object, synchronization guarantees exclusion but it does not guarantee order of execution. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Constructors and Synchronization • Constructors are not synchronized because it is executed only when creating an object, which can only happen in one thread executing the creation. • Constructors cannot be synchronized. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Synchronization • Always lock during updates to objects. • Always lock during access of possibly updated object fields. • Never lock when invoking methods on other objects. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Basic concepts: semaphores A semaphore is any object that two threads can use to synchronize with one another. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
The mutex (mutual-exclusion semaphore) • The mutex is the key to a lock • Though it is sometimes called a “lock.” • Ownership is the critical concept • To cross a synchronized statement, a thread must have the key, otherwise it blocks (is suspended). • Only one thread can have the key (own the mutex) at a time. • Every Object contains an internal mutex: Object mutex = new Object(); synchronized( mutex ) { // guarded code is here. } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Monitors and airplane bathrooms • A monitor is a body of code (not necessarily contiguous), access to which is guarded by a single mutex. • Every object has its own monitor (and its own mutex). • Think “airplane bathroom” • Only one person (thread) can be in it at a time • Locking the door acquires the associated mutex. • You can't leave without unlocking the door. • Other people must line up outside the door if somebody's in there. • Acquisition is not necessarily FIFO order. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Synchronization with individual locks • Enter the monitor by passing over the synchronized keyword: I.e. acquiring an object’s lock, • Object has a synchronized method and a client invoked such method • Client is executing a synchronized block on an object • Entering the monitor does not restrict access to objects used inside the monitor—it just prevents other threads from entering the monitor. long field; Object lock = new Object(); synchronized(lock) { field = new_value; } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Method-level synchronization • The monitor is associated with the object, not the code. • Two threads can happily access the same synchronized code at the same time, provided that different objects receive the request. • E.g. Two threads can enqueue to different queues at the same time, but they cannot simultaneously access the same queue: • Same as synchronized(this) class Queue{ public synchronized void enqueue(Object o){ /*…*/ } public synchronized Object dequeue(){ /*…*/ } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
He came in the Bathroom Window • The Bathroom can have several doors class Bathroom_window{ private double guard_this; public synchronized void ringo(double value){ guard_this = value; } public double george(){// WRONG! return guard_this; // needs synchronization } } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Constructors can’t be synchronized, so always have back doors class Unpredictable{ private final int x; private final int y; public Unpredictable(int init_x, int init_y){ new Thread(){ public void run(){ System.out.println(“x=“ + x + “ y=“ + y); } }.start(); x = init_x; y = init_y; } } • Putting the thread-creation code at the bottom doesn’t help (the optimizer might move it). Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Locking the constructor’s back door class Predictable{ private final int x; private final int y; public Predictable(int init_x, int init_y){ synchronized( this ){ new Thread(){ public void run(){ synchronized( Predictable.this){ System.out.println(“x=“+x+“ y=“+y); } } }.start(); x = init_x; y = init_y; } } } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Synchronization isn’t cheap class Synch { synchronized int locking ( int a, int b ) { return a + b;} int not_locking ( int a, int b ) { return a + b;} static public void main(String[] arguments){ Synch tester = new Synch(); double start = new Date().get Time(); for(long i = 1000000; --i >= 0 ;) tester.locking(0,0); double end = new Date().getTime(); double locking_time = end - start; // repeat for not_locking } } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Synchronization isn’t cheap • % java -verbose:gc Synch • Pass 0: Time lost: 234 ms. 121.39% increase • Pass 1: Time lost: 139 ms. 149.29% increase • Pass 2: Time lost: 156 ms. 155.52% increase • Pass 3: Time lost: 157 ms. 155.87% increase • Pass 4: Time lost: 157 ms. 155.87% increase • Pass 5: Time lost: 155 ms. 154.96% increase • Pass 6: Time lost: 156 ms. 155.52% increase • Pass 7: Time lost: 3,891 ms. 1,484.70% increase • Pass 8: Time lost: 4,407 ms. 1,668.33% increase • 200MHz Pentium, NT4/SP3, JDK 1.2.1, HotSpot 1.0fcs, E • • Contention in last two passes (Java Hotspot can’t use • atomic-bit-test-and-set). Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Synchronization isn’t cheap BUT • The cost of poor design is always higher than the cost of synchronization. • Pick a fast algorithm. • Overhead can be insignificant when the synchronized method is doing a time-consuming operation. • But in OO systems, small synchronized methods often chain to small synchronized methods. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Avoiding synchronization • Reentrant code doesn’t need to be synchronized. • Code that uses only local variables and arguments (no static variables, no fields in the class). • Atomic operations do not need to be synchronized, but beware of reordering. • Assignment to all non-64-bit things, including booleans and references are usually safe, but sequence not preserved. • Must be declared volatile, but volatile might not work. • Assignment to volatile doubles and floats should be atomic (but most JVMs don’t do it). • Code may be reordered, so assignment to several atomic variables must be synchronized. • Sequence of volatile operations should be preserved, but usually isn’t. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Avoiding synchronization • Synchronize the smallest block possible to minimize the odds of contention. • Method-level synchronization should be avoided in very-high-performance systems. • Don’t synchronize the methods of classes that are called only from one thread. • Use Collection-style synchronization decorators when you need synchronized behavior. • Collection c = new ArrayList(); • c = Collections.synchronizedCollection(c); Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads