Lecture 4

▶︎
all
running...

Threads

Threads in Java

Each thread must have a method:

void run(){...}

Creating a Thread

Example of a Runnable Class
public class Hello implements Runnable {
    String message;
    public Hello(String m){
        this.message = m;
    }

    public void run(){
        System.out.println(this.message);
    }
}

Instantiating a Thread
String m = "Hello from " + i; 
Runnable h = new Hello(m)
Thread t = new Thread(h)

However this is very verbose and can be streamlined using Java's anonymous inner class syntax.

t = new Thread(
        new Runnable() {
            public void run() {
                System.out.println(m);
            }
        }
);

This can be streamlined furter still by using Java's lambda functions:

t = new Thread(
        () -> {
            System.out.println(m);
        }
)

Starting a thread can be achieved using the method t.start();
Similarly, joining a thread is possible through t.join();

Monitors

Concurrent access Queue Implementation

public class Queue<T>{
    private static final int QSIZE = 10;
    private QueueObject<T> head;
    private QueueObject<T> tail;
    private int item_count;

    Queue() {
        item_count = 0;
        this.head = null;
        this.tail = null;
    }

    private boolean isEmpty() {
        return head == null;
    }

    public synchronized void enq(T x) throws InterruptedException {
        while (item_count == QSIZE) { //while full
            wait();
        }

        QueueObject<T> newItem = new QueueObject<>(x);
        if (isEmpty()) {
            this.head = newItem;
            this.tail = newItem;
        } else {
            this.tail.setNext(newItem);
            this.tail = newItem;
        }
        notifyAll(); //let dequeuers know there are now items to dequeue
        item_count++;
    }

    public synchronized T deq() throws InterruptedException {
        while (isEmpty()) { //while empty
            wait();
        }

        QueueObject<T> ret = this.head;
        this.head = this.head.getNext();

        item_count--;
        notifyAll(); //let enqueuers know that there is definitely space now
        return ret.getVal();

    }


}

class QueueObject<T> {
    private T val;

    T getVal() {
        return val;
    }

    void setNext(QueueObject<T> next) {
        this.next = next;
    }

    public QueueObject<T> getNext() {
        return next;
    }

    private QueueObject<T> next;

    QueueObject(T val) {
        this.next = null;
        this.val = val;
    }
}

This is an optimal implementation and satisfies the following:

Mutual Exclusion

void foo(){
    ...
    syncronized (this) {
        ...
    }
    ...
}

Waiting

Spinning

while (itemCount == QSIZE){} //wait

Blocking

The wait() method

try{
    q.wait(); // throws InterrupedException 
} catch (InterruptedException e){
    ...
}

Usage

...
while (item_count == QSIZE) { //while full
        wait();
}
...

Awakening

q.notify();
q.notifyAll();

Note: In practice, always use notifyAll() to avoid lost wakeup bugs

Thread local data

Thread local data in Java

Thread local methods

Threadlocal<T> local;
T x = ...;
local.set(x);
Threadlocal<T> local;
T x = local.get();
T x = local.initialValue()

Thread local IDs

package threadMisc;

public class ThreadID {
    private static volatile int nextID = 0;

    private static class ThreadLocalID extends ThreadLocal<Integer>{
        protected synchronized Integer initialValue(){
            return nextID++;
        }
    }

    private static ThreadLocalID threadID = new ThreadLocalID();
    public static int get(){
        return threadID.get();
    }
}