1. "Hello, World!"
  2. Variables and Types
  3. Arrays
  4. While, If, For
  5. ...Problem Set 0
  6. Static Methods
  7. Static Fields
  8. String Conversion
  9. Objects
  10. Threading
  11. Strings
  12. ...Problem Set 1.5
  13. Packages
  14. Complex Numbers
  15. Abstract classes
  16. Interfaces
  17. Autoboxing
  18. ...Problem Set 1
  19. enum
  20. Inner Classes
  21. Polymorphism
  22. Tanks!
  23. Callbacks
  24. Exceptions
  25. File I/O
  26. ...Problem Set 2
  27. Regular Expressions

Threading

Modern computers are capable of threading -- i.e. performing several tasks at the same time using the same memory.

In this first example we create two threads, each of which will update the static variable i 10,000 times.

Creating threads is easy. We subclass java.lang.Thread, then supply a run() method. We call start() to get the thread running, and we call join() to wait for it to stop. Easy.

Thread1.java
1public class Thread1 extends Thread {
2    static int i = 0;
3    public void run() {
4        for(int j=0;j<10000000;j++)
5            i++;
6    }
7    public static void main(String[] args) throws InterruptedException {
8        long time1 = System.nanoTime();
9        Thread t1 = new Thread1();
10        Thread t2 = new Thread1();
11        t1.start();
12        t2.start();
13        t1.join();
14        t2.join();
15        System.out.println("i="+i);
16        long time2 = System.nanoTime();
17        System.out.println("time = "+(time2-time1));
18    }
19}
$ javac Thread1.java
$ java Thread1
i=10021830
time = 21107837

What went wrong? The operation "i++" is actually performed in steps, first a read, then an increment, then a write back to main memory. It may happen that thread 2 reads a value directly after thread 1 does, before thread 1 has a chance to do an increment. In this case one of the increments are lost.

This is called a race condition, and it can be very difficult to debug.

Let's try calling the run() methods directly, without creating a thread.

Thread1a.java
1public class Thread1a extends Thread { 
2    public static void main(String[] args) throws InterruptedException {
3        long time1 = System.nanoTime();
4        Thread t1 = new Thread1();
5        Thread t2 = new Thread1();
6        t1.run();
7        t2.run();
8        System.out.println("i="+Thread1.i);
9        long time2 = System.nanoTime();
10        System.out.println("time = "+(time2-time1));
11    }
12}
$ javac Thread1a.java
$ java Thread1a
i=20000000
time = 17784142

Because we never called start, we never really ran any new threads. This solved our race condition, of course, because we only ever had one thread manipulating the data.

Ok, that was a lot slower. We want to go back to using start()/join(), but without the race condition occurring.

There are several ways we can go about fixing this.

Thread2.java
1public class Thread2 extends Thread {
2    static int i = 0;
3    static synchronized void inc() {
4        i++;
5    }
6    public void run() {
7        for(int j=0;j<10000000;j++)
8            inc();
9    }
10    public static void main(String[] args) throws InterruptedException {
11        long time1 = System.nanoTime();
12        Thread t1 = new Thread2();
13        Thread t2 = new Thread2();
14        t1.start();
15        t2.start();
16        t1.join();
17        t2.join();
18        System.out.println("i="+i);
19        long time2 = System.nanoTime();
20        System.out.println("time = "+(time2-time1));
21    }
22}
$ javac Thread2.java
$ java Thread2
i=20000000
time = 966481550

The notion here is that we were able to synchronize our access to the variable "i".

Synchronization is a very helpful tool, and it can prevent race conditions -- but it has its own dangers.

Thread3.java
1public class Thread3 extends Thread {
2    static Thread3 ab;
3 
4    int i = 0;
5 
6    public void run() {
7        for(int i=0;i<10000000;i++)
8            if(i % 2 == 0)
9                inc_a_then_b();
10            else
11                inc_b_then_a();
12    }
13 
14    void inc_a_then_b() {
15        synchronized(a) {
16            a.i++;
17            synchronized(b) {
18                b.i++;
19            }
20        }
21    }
22 
23    void inc_b_then_a() {
24        synchronized(b) {
25            b.i++;
26            synchronized(a) {
27                a.i++;
28            }
29        }
30    }
31 
32    public static void main(String[] args) throws InterruptedException {
33        long time1 = System.nanoTime();
34        a = new Thread3();
35        b = new Thread3();
36        a.start();
37        b.start();
38        a.join();
39        b.join();
40        System.out.println("i="+a.i);
41        long time2 = System.nanoTime();
42        System.out.println("time = "+(time2-time1));
43    }
44}
$ javac Thread3.java

I can't show you what this code does. You'll have to try it for yourself. It may take more than one attempt, but eventually you'll see it. The code just stops and you get no output.

What went wrong? Suppose thread a has synchronized on itself and thread b has synchronized on itself, and then each waits for the lock on the other. They will wait forever. This is called deadlock.

Threading looks deceptively simple, but almost any task that you implement using thread and synchronized is fraught with peril.