170 likes | 195 Views
Learn about confinement techniques to structurally control object access across threads in distributed software engineering. Understand encapsulation concepts to guarantee unique access, prevent reference leakage, and ensure method confinement.
E N D
Confinement Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Encapsulation technique to structurally guarantee that at most one activity (thread) at a time can possibly access a given object. • Ensure uniqueness of access • No need to rely on dynamic locking (synch) Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Reference Leakage • A reference r to an object x can escape from method m executing some activity: • m passes r as an argument to a method • m passes r as the return value from a method invocation • m records r in some field accessible from other activity ( worst case: static fields) • m releases another reference that can be traversed to access r. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Allowed leakage • Some leakages are allowed if they guarantee no state changes. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement across methods • If a given method creates an object, and does not let it escape, thus no other thread will interfere with it: • Hiding access within local scope • You may allow to escape as a tail-call: original method will have no more use of reference. • Hand-off protocol: at any time, at most one actively executing method can access an object: • Tail call • Factory methods. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement across methods • Sessions • A public method creates an object confined to a sequence of operation comprising the service • Method performs any cleanup needed via a finally clause Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement across methods • Multiple calls: tail calls do not apply if calling method needs to access object after call. • Caller copies -- identity is not needed • Receiver copies -- ditto • Using scalar arguments instead of references: • Provide enough information for receiver to construct object if need by. • Trust Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within threads class ThreadPerSessionBasedService { // fragments // ... public void service() { Runnable r = new Runnable() { public void run() { OutputStream output = null; try { output = new FileOutputStream("..."); doService(output); } catch (IOException e) { handleIOFailure(); } finally { try { if (output != null) output.close(); } catch (IOException ignore) {} } } }; new Thread(r).start(); } void handleIOFailure() {} void doService(OutputStream s) throws IOException { s.write(0); // ... possibly more hand-offs ... } }// end ThreadPerSessionBasedService • Thread-per-session design. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within threads • Thread-specific fields: • Static method Thread.currentThread() • Add fields to thread subclass, and access them within current thread. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
class ThreadWithOutputStream extends Thread { private OutputStream output; ThreadWithOutputStream(Runnable r, OutputStream s) { super(r); output = s; } static ThreadWithOutputStream current() throws ClassCastException { return (ThreadWithOutputStream) (currentThread()); } static OutputStream getOutput() { return current().output; } static void setOutput(OutputStream s) { current().output = s;} } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
class ServiceUsingThreadWithOutputStream { // Fragments // ... public void service() throws IOException { OutputStream output = new FileOutputStream("..."); Runnable r = new Runnable() { public void run() { try { doService(); } catch (IOException e) { } } }; new ThreadWithOutputStream(r, output).start(); } void doService() throws IOException { ThreadWithOutputStream.current().getOutput().write(0); } } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
class ServiceUsingThreadLocal { // Fragments static ThreadLocal output = new ThreadLocal(); public void service() { try { final OutputStream s = new FileOutputStream("..."); Runnable r = new Runnable() { public void run() { output.set(s); try { doService(); } catch (IOException e) { } finally { try { s.close(); } catch (IOException ignore) {} } } }; new Thread(r).start(); } catch (IOException e) {} } void doService() throws IOException { ((OutputStream)(output.get())).write(0); // ... } } ThreadLocal Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within threads • Housing object references in Thread objects allow methods running in the same thread to share them freely • Thread specific variables hide parameters and makes hard error checking and leakage • No synchronization needed, but • Hinders reusability as it increases coupling. Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within objects • Can confine all access to be only internal to an object, so no further locking is needed. • Exclusive control of host of container object propagates to its internal parts. • Need synchronization at all entry points in the Host object. • Host own the parts, or parts are “contained” in Host. • Host constructs new instances of the parts, • Host does not leak references • Best host: all parts are fixed. No need for updates. • Typical implementation of such Host: • Use of adapters • Use of subclassing Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within groups: Adapters • Can be used o wrap bare unsynchronized objects within fully synchronized Hosts. class BarePoint { public double x; public double y; } class SynchedPoint { protected final BarePoint delegate = new BarePoint(); public synchronized double getX() { return delegate.x;} public synchronized double getY() { return delegate.y; } public synchronized void setX(double v) { delegate.x = v; } public synchronized void setY(double v) { delegate.y = v; } } • The Java.util.collection framework uses adapter-based allowing layered synchronization of collection classes. Except for Vector and Hashtable, basic collection classes are unsynchronized. Synchronized adapters can be constructed: • List l = Collections.synchronizedList(new ArrayList()); Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within groups: Adapters • When you cannot guarantee containment, define multiple versions of a class and instantiate appropriately for given usage. class SynchronizedAddress extends Address { // ... public synchronized String getStreet() { return super.getStreet(); } public synchronized void setStreet(String s) { super.setStreet(s); } public synchronized void printLabel(OutputStream s) { super.printLabel(s); } } class Address { // Fragments protected String street; protected String city; public String getStreet() { return street; } public void setStreet(String s) { street = s; } // ... public void printLabel(OutputStream s) { } } Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads
Confinement within groups • Groups of objects accessible across multiple threads can together ensure that only one of them can access a given resource: • Tokens • Batons • Linear objects • Capabilities • Resources • These groups need strict protocols to manage the resource: • Acquire • Forget • Put (give) • Take • Exchange Distributed Software Engineering C:\unocourses\4350\slides\DefiningThreads