java thread

Constructing Threads and Runnables

In our Java threading introduction, we created a thread in twosteps:

  • firstly, we constructed a Runnable object to define the code tobe executed by the thread;
  • then, we constructed a Thread object around the Runnable.

There are actually a couple of variations on this pattern of thread constructionthat we'll look at here.

Pattern 1: create an explicit class that implements Runnable

The Runnable implementations that we created were inline classes.That is, we didn't spell out a full class declaration. But strictly speaking, the way tocreate a Runnable— or rather, a class that implements the Runnableinterface— is as follows:

public class MyTask implements Runnable {
  public void run() {
    ...
  }
}
...
Runnable r = new MyTask();
Thread thr = new Thread(r);

If we just write new Runnable(), the compiler under the hood actually createsa "dummy class" of the above form for us. But sometimes it's useful to create our own class.In our simultaneous message printing example, both Runnables essentially had similarcode: only the message and time interval differed.So it would be neater to define a class that took the message and interval as parameters tothe constructor:

public class MessagePrinter implements Runnable {
  private final String message;
  private final long interval;
  public MessagePrinter(String msg, long interval) {
    this.message = msg;
    this.interval = interval;
  }
  public void run() {
    try {
      while (true) {
        System.out.println(message);
        Thread.sleep(interval);
      }
    } catch (InterruptedException iex) {}
  }
}

Notice that for reasons we'll come to, we declare the variables final.This is basically a means of making sure they are "seen properly" by the two threads involved(the thread that constructs the object, then the thread in which run() willactually be running when we start the thread).

Pattern 2: override Thread.run()

You can actually dispense with the separate Runnable method. The Thread classhas a (normally empty) run() method. If you don't passin a Runnable to the constructor, then the run() method of Thread willbe called instead when the thread starts. So we could write something like this:

public void MyThread extends Thread {
  public void run() {
    ...
  }
}
...
Thread thr = new MyThread();
thr.start();

Of course, we can also turn this into an inline class:

Thread thr = new Thread() {
  public void run() {
    ...
  }
}
thr.start();

Which thread construction pattern?

So, which method should you use to construct a thread in Java? In general, constructing aseparate Runnable gives you more flexibility. Running in a Thread turns out notto be the only way of running a Runnable, so if you embed everything insidea Thread object from the beginning, you may end up with more code to change later onif you decide to do things differently. In the simplest case, having a separate Runnable allows you towrite code such as the following:

public void runTask(Runnable r, boolean separateThread) {
  if (separateThread) {
    (new Thread(r)).start();
  } else {
    r.run();
  }
}

Other instances where a Runnable is used are with the Swing.invokeLater() method(called from a non-Swing thread to ask Swing to run a particular task in its UI thread), orwith various executor utilities introduced in the Java 5 concurrency package.

On the other hand, for threads representing fairly "major" tasks running right throughyour application, where it's clear from the ground up that you don't need the flexibility ofthe separate Runnable object, just overriding Thread.run() may make yourcode a little less cluttered.


Thread methods in Java

On the previous page, we looked at how toconstruct a thread in Java, via the Runnable and Thread objects. We mentionedthat the Thread class provides control over threads. So on this page,we take a high-level look at the most important methods on this class.

Thread.sleep()

We actually saw a sneakpreview of Thread.sleep() in our Java threading introduction.This static method asks the system to put the current threadto sleep for (approximately) the specified amount of time, effectively allowing usto implement a "pause". A thread can be interruptedfrom its sleep.

For more details, see: Thread.sleep() (separate page).

interrupt()

As mentioned, you can call a Thread object's interrupt() methodto interrupt the corresponding thread if it is sleeping or waiting. The correspondingthread will "wake up" with an IOException at some point in the future.See thread interruption for more details.

setPriority() / getPriority()

Sets and queries some platform-specific priority assignment of the given thread.When calculating a priority value, it's good practice to always do so in relation to theconstants Thread.MIN_PRIORITY, Thread.NORM_PRIORITY andThread.MAX_PRIORITY. In practice, values go from 1 to 10, and map on tosome machine-specific range of values: nice values in the case ofLinux, and local thread priorities in the case of Windows. These are generallythe range of values of "normal" user threads, and the OS will actually still runother threads beyond these values (so, for example, you can't preemptthe mouse pointer thread by setting a thread to MAX_PRIORITY!).

Three main issues with thread priorities are that:

  • they don't always do what you might intuitively think they do;
  • their behaviour depends on the platform and Java version:e.g. in Linux, priorities don't work at all in Hotspot before Java 6, andthe mapping of Java to OS priorities changed underWindows between Java 5 and Java 6;
  • in trying to use them for some purpose, you may actuallyinterfere with more sensible scheduling decisions that the OS would have made anywayto achieve your purpose.

For more information, see the section on thread scheduling and the discussion on thread priorities, wherethe behaviour on different platforms is compared.

join()

The join() method is called on the Thread object representingenother thread. It tells the current thread to wait for the other threadto complete. To wait for multiple threads at a time, you can use a CountDownLatch.

Thread.yield()

This method effectively tells the system that the current thread is "willing torelinquish the CPU". What it actually does is quite system-dependent.For more details, see: Thread.yield() (separate page).

setName() / getName()

Threads have a name attached to them. By default, Java will attach a fairly dullname such as Thread-12. But for debugging purposes you might want toattach a more meaningful name such as Animation Thread, WorkerThread-10 etc.(Some of the variants of the Thread constructor actually allow you to pass in a namefrom the start, but you can always change it later.)

Other thread functions

There are occasionally other things that you may wish to do with a thread thatdon't correspond to a single method. In particular, there is nosafe method to stop a thread, and instead youshould simply let the corresponding run() method exit. 


Thread interruption in Java

In our overview of thread methods, we saw variousmethods that throw InterruptedException.

Interruption is a mechanism whereby a thread that is waiting (or sleeping) can be made to prematurely stop waiting.

Incidentally, it is important not to confuse thread interruptionwith either software interrupts (where the CPU automaticallyinterrupts the current instruction flow in order to call a registered piece of code periodically—as in fact happens to drive the thread scheduler)and hardware interrupts (where the CPU automatically performs a similartask in response to some hardware signal).

To illustrate interruption, let's consider again a thread that prints a messageperiodically. After printing the message, it sleeps for a couple of seconds, thenrepeats the loop:

Runnable r = new Runnable() {
  public void run() {
    try {
      while (true) {
        Thread.sleep(2000L);
        System.out.println("Hello, world!");
      }
    } catch (InterruptionException iex) {
      System.err.println("Message printer interrupted");
    }
  }
};
Thread thr = new Thread(r);
thr.start();

The InterruptedException is thrown by the Thread.sleep()method, and in fact by a few other core library methods that can "block", principally:

  • Object.wait(), part of the wait/notify mechanism;
  • Thread.join(), that makes the current thread wait for another thread to complete;
  • Proess.waitFor(), which lets us wait for an external process (started from our Java application) to terminate;
  • various methods in the Java 5 concurrency libraries, such asthe tryLock() method of theJava 5 ReentrantLock class.

In general, InterruptedException is thrown when another thread interrupts thethread calling the blocking method. The other thread interrupts the blocking/sleeping thread by callinginterrupt() on it:

thr.interrupt();

Provided that the thread or task calling sleep() (or whatever) has been implemented properly,the interruption mechanism can therefore be used as a way to cancel tasks.

Next: interruption during non-blocking calls and InterruptionException

On the next page, we'll look at a couple of further details that we haven't coveredin the basics above:

Threading with Swing

You may have written a GUI-enabled application using a framework such asSwing, and never even given a thought to threads. And for some types of application,you pretty much don't have to. If everything in your application happens directlyin response to a button click or other Swing event, and your application never"does things with threads" (it never sleeps, spawns another thread or process,"does something in the background" etc), then be it by good design or good fortune, it may bebehaving correctly. But there are times, particularly if your applicationdoes want to "do something with threads", when you need to understand howthreading works with respect to the GUI system.

As you'll know if you've programmed with GUI systems such as Swing, everythinggenerally happens in response to events. You define an eventhandler, which is effectively the code you would like to be called whensomething happens. For example, we can define a button like this:

JButton b = new JButton("Click me");
b.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    // Code will be performed when button clicked
  }
});

To our program, the call to our method just "comes out of nowhere". Butin reality, it must come from somewhere. That is: there must be some particularthread that's calling into our method. Well, there is, and it's quitea well-defined thread.

The event dispatch thread

The thread making calls to our event handlers is the event dispatch thread.This is a special thread that the GUI system sets up for performing UI tasks. Essentially, alluser interface code will be executed by this special thread. Having a single designatedthread handling the entire UI avoids a lot of issues that would occur if we tried toallow, say, different event handlers to be called by arbitrary threads.

In most Swing applications, if you've not thought about it and done anything specialto use other threads, then practically all of your application will probably happen in the event dispatch thread. What you might not have thought about is whether it'sall happening correctly...

Proper threading behaviour of GUI applications

There are essentially two rules of thumb that you need to remember:

  • always manipulate your user interface from the event dispatch thread(with one or two safe exceptions);
  • never block or delay the event dispatch thread— in other words,never call methods such as Thread.sleep(), Object.wait(),Condition.await() inside an event handler.

In more detail, these two guidelines have a few implications:

  • all tasks that we perform inside an event handler should beinstantaneous; we should not perform a long-running task(such as making a database query) or call Thread.sleep()or Object.wait() or similar callsdirectly from an event handler;
  • to perform such long-running tasks, we need to arrnage for them tohappen in another thread, e.g. bystarting a new thread specially for the task;
  • we need to be wary of the more "subtle" library calls that mightcause our thread to wait: if we use one of the concurrency utilities such asa ThreadPoolExecutor, we need to make surewe use a variant that won't ever block waiting for room in the queue;other slightly more obvious no-nos are awaiting a latch,joining another thread, calling Future.get()... essentiallyanything that "waits" for something cannot be called inside an eventhandler;
  • if we're in our other thread and need to update the UI (e.g. to reportprogress to the user), we generally need to arrange for that update code— andonly the update code— to happen in the event dispatch thread;
  • by "manipulating the UI", we mean calling methods on or changing the stateof any Swing components butalso modifying any objects they depend on such as table models,cell renderers etc; firing events must also happen in the event dispatchthread.

How do you make something happen in the event dispatch thread?

So far, we've glibly said that in certain cases we mustrun something on the event dispatch thread. But how do we actually do that?On the next page, we look at the special methodSwingUtilities.invokeLater() which providesus with this functionality. 

How threads work: more details

In our introduction to Java threads, we showed the basics of howto start a thread, and the idea that threads let us run multiple tasks or "mini-programs" inparallel. But to understand certain thread programming issues in more detail, it's helpfulto take a more detailed look at what threads actually are and how they work.

Figure 1: Typical relationsip between
threads and processes
.

Threads and processes

A thread is essentially a subdivision of a process, or "lightweight process" (LWP)on some systems. A process is generally the most major and separate unit of executionrecognised by the OS. The typical relationship between processes, threads and various other elements of the OS are shown in Figure 1 opposite. This shows two processes, each split into two threads (a simplistic situation, of course: there will be typically dozens of processes, some with dozens or more threads).

Crucially, each process has its own memory space. When Process 1 accesses somegiven memory location, say 0x8000, that address will be mapped to some physical memoryaddress1. But from Process 2, location 0x8000 will generally refer to a completely different portionof physical memory.A thread is a subdivision that shares the memory space ofits parent process. So when either Thread 1 or Thread 2 of Process 1 accesses "memory address 0x8000",they will be referring to the same physical address. Threads belonging to a process usually sharea few other key resources as well, such as their working directory,environment variables, file handles etc.

On the other hand, each thread has its own private stack andregisters, including program counter. These areessentially the things that threads need in order to be independent.Depending on the OS,threads may have some other private resources too, such as thread-local storage(effectively, a way of referring to "variable number X", where each thread has its ownprivate value of X). The OS will generally attach a bit of "housekeeping" information to eachthread, such as its priority and state (running, waiting for I/O etc).

The thread scheduler

There are generally more threads than CPUs. Part of a multithreadedsystem is therefore a thread scheduler, responsible forsharing out the available CPUs in some way among the competing threads.Note that in practically all modern operating systems, the thread scheduler is partof the OS itself.So the OS actually "sees" our different threads and is responsible for the taskof switching between them2.The rationale for handling threading "natively" in the OS is that the OS is likely to havethe information to make threading efficient (such as knowing which threads are waiting forI/O and for how long), whereas a software library may not have this information available.In the rest of our discussion, we'll generally assume this native threads model. 

Thread Scheduling

In our introduction to how threads work, weintroduced the thread scheduler, part of the OS (usually) that isresponsible for sharing the available CPUs out between the various threads.How exactly the scheduler works depends on the individual platform, but variousmodern operating systems (notably Windows and Linux) use largely similar techniquesthat we'll describe here. We'll also mention some key varitions between the platforms.

Note that we'll continue to talk about a single thread scheduler. On multiprocessorsystems, there is generally some kind of scheduler per processor, which thenneed to be coordinated in some way. (On some systems, switching on different processors is staggered toavoid contention on shared scheduling tables.)Unless otherwise specified, we'll use the term thread scheduler to refer tothis overall system of coordinated per-CPU schedulers.

Across platforms, thread scheduling1 tends to be based on at least the followingcriteria:

  • a priority, or in fact usually multiple"priority" settings that we'll discuss below;
  • a quantum, or number of allocated timeslices of CPU,which essentially determines theamount of CPU time a thread is allotted before it is forced to yield theCPU to another thread of the same or lower priority (the system will keep trackof the remaining quantum at any given time, plus its defaultquantum, which could depend on thread type and/or system configuration);
  • a state, notably "runnable" vs "waiting";
  • metrics about the behaviour of threads, such asrecent CPU usage or the time since it last ran (i.e. had a shareof CPU), or the fact that it has "just received an event it was waiting for".

Most systems use what we might dub priority-based round-robinscheduling to some extent. The general principles are:

  • a thread of higher priority (which is a function of base and local priorities)will preempt a thread of lower priority;
  • otherwise, threads of equal priority will essentially take turnsat getting an allocated slice or quantum of CPU;
  • there are a few extra "tweaks" to make things work.

States

Depending on the system, there are various states that a threadcan be in. Probably the two most interesting are:

  • runnable, which essentially means "ready to consume CPU";being runnable is generally the minimum requirement for a threadto actually be scheduled on to a CPU;
  • waiting, meaning that the thread currently cannot continueas it is waiting for a resource such as a lock or I/O, for memory to be paged in,for a signal from another thread, or simply for a period of time to elapse(sleep).

Other states include terminated, which means the thread's code hasfinished running but not all of the thread's resources have been cleared up,and a new state, in which the thread has been created, but not all resourcesnecessary for it to be runnable have been created. Internally, the OS maydistinguish between various different types of wait states2 (for example "waiting for a signal"vs "waiting for the stack to be paged in"), but this level of granularity is generally notavailable or so important to Java programs. (On the other hand, Java generally exposesto the programmer things the JVM can reasonly know about, for example,if a thread is waiting to acquire the lock on a Java object— roughly speaking,"entering a synchronized block".) 


Next

Stopping a thread

On the previous pages, we focussed on how tostart a thread in Java. We saw that after creating the Thread object,calling start() asynchronously starts the corresponding thread. In our example,the run() method contained an infinite loop. But in real life, we generallywant our thread to stop. So how do we make our thread stop?

Firstly, the thing you shouldn't do is call Thread.stop().This method, now deprecated, was intended to stop a given thread abruptly. But the problemwith this is that the caller can't generally determine whether or not the giventhread is at a safe point to be stopped. (This isn't just a Java phenomenon:in general, the underlying operating systemcalls that Thread.stop() makes to abruptly stop the threadare also deprecated for this reason.)

So how can we stop a thread safely? In general:

To make the thread stop, we organise for the run() method to exit.

There are a couple of ways that we would typically do so.

Use a "stop request" variable

A common solution is to use an explicit "stop request" variable, which wecheck on each pass through the loop. This technique is suitable provided that wecan check the variable frequently enough:

private volatile boolean stopRequested = false;

public void run() {
  while (!stopRequested) {
    ...
  }
}

public void requestStop() {
  stopRequested = true;
}

Note that we must declare the stopRequested variableas volatile, because it isaccessed by different threads.

Use Thread.interrupt()

The above pattern is generally suitable if the variable stopRequestedcan be polled frequently. However, there is an obvious problem ifthe method blocks for a long time, e.g. by callingThread.sleep() or waiting on an object.In general, such blocking methods are interruptible. For moreinformation, see the section on thread interruption.

Organise for the thread to receive a queued "stop" message

In a typical producer-consumerpattern, where a thread blocks waiting on a queue (e.g. aBlockingQueue), we couldorganise for the thread to interpret some special object on the queue to mean"please shut down". To ask the thread to shut down, we therefore post thespecial message object to the queue. The advantage of this method is that the threadwill finish processing messages already on the queue before shutting down. 

Synchronization and thread safety in Java

As soon as we start using concurrent threads, we need to think about variousissues that fall under the broad description of thread safety.Generally, we need to take steps tomake sure that different threads don't interact in negative ways:

  • if one thread is operating on some data or structure, we don't want another threadto simultaneously operate on that same data/structure and corrupt the results;
  • when Thread A writes to a variable that Thread B accesses, we need tomake sure that Thread B will actually see the value written by Thread A;
  • we don't want one thread to hog, take or lock for too longa resource that other threads need in order to make progress.

The rest of this section gives an overview of techniques used to achievethese goals. More detailed information is available on the pages linked to below,and in the following sections:

Synchronization and locking

A key element of thread safety is locking access to shared data whileit is being operated on by a thread. Perhaps the simplest— but not alwaysthe most versatile— way of doing this is via the synchronized keyword.The essential idea is that if we declare a method synchronized,then other synchronized methods cannot be simultaneously called onthe same object. For example, this class implements a thread-safe random numbergenerator:

public void RandomGenerator {
  private long x = System.nanoTime();

  public synchronized long randomNumber() {
    x ^= (x << 21);
    x ^= (x >>> 35);
    x ^= (x << 4);
    return x;
  }
};

If multiple threads concurrently try to call randomNumber(),only one thread at a time will actually execute it; the others will effectively"queue up" until it's their turn1. This is because the thread actuallyexecuting the method at any given time owns a "lock" on the RandomGeneratorobject it is being called on. For the generator to work, we need all three operationson x to happen "atomically"— i.e. without concurrent modifications to xfrom another thread. Without the synchronization, we would have a data race:two concurrent calls to randomNumber() could interfere with each other'scalculation of x.Synchronizing also crucially means that the result of xcalculatedin one thread is visible to other threads calling the method: see below.

It is also possible to synchronize any arbitraryblock of code on any given object: for more details, see the section on theJava synchronizedkeyword.

Explicit locks

The built-in synchronization mechanism has some limitations. For example, a threadwill potentially block forever waiting to acquire the lock on an object (e.g. if the threadowning the lock gets into an infinite loop or blocks for some other reason). For morefine-grained control over synchronization, Java 5 introduced explicitLock objects.

Cooperation

Whether via the synchronized keyword or an explicit Java Lock,data sharing with object locking is a cooperative process: that is, allcode accessing (reading or writing) the dependent data must synchronize.

Publication and data visibility

A common misconception is that thread safety is just about data races.A less well understood issue is visibility. Ordinarily, writinga value to some variable from Thread A doesn't guarantee that the new value will be immediatelyvisible from Thread B, or even visible at all. And accessing multiple variables doesn'tnecessarily happen in "program order".For various reasonssuch as compiler optimisations and CPU memory cache behaviour(see the section on processor architectureand synchronization), we need toexplicitly deal with data visibility when data is to be accessed bymultiple threads and/or when order of access is important,whether the access is concurrent or not. For example, we need totake steps when:

  • an object or value is created by one thread then used by another(even where we don't expect the accesses to be concurrent);
  • one thread uses a variable such as a flag to signal to another thread;
  • we read from variable A, then write to variable B, and we strictly expect the first to"happen before" the second.

In general, correct synchronization solves the visibility problem,and many of the bugs that occur are when people think they've found a clever (but broken)way to avoid synchronization.For more details on visibility, see the section onvariable synchronization.

After Thread A exits a block synchronized on Object X,that Thread B when entering a block also synchronized on Object X will seethe data as it was visible to thread A when it exited the block.

Implicit in this description is that the synchronized blocks guarantee ordering:data accesses from Thread A will strictly "happen before" data accesses in Thread B.

Java provides two other keywords, volatile and final,that in the right circumstances can be used to guarantee visibility:

  • if all of the fields on an object are final, then that objectcan be safely read from any thread without synchronization;
  • if a variable is declared volatile, then this signals that the variablewill be accessed by multiple threads, and also gives visibility guarantees:for more details, see the section on theJava volatile keyword, plusour discussion of when to use volatile



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值