courses:cs334-201503:lectures:threadpools-thefinalchapter.docx (39.3 KB)

advertisement
Cmpu-334
Fall, 2015
A series of 5 programming concepts to explore
1. For any given (java) application, what is the best number of threads to use?
When there is an application that can use N threads to perform different tasks, it seems silly to
use N=1. That’s because it is silly. When there is a plethora of tasks to run, the best answer
involves finding out how many CPU’s (processors) exist on a given machine. However, that
number is not constant. We cannot just say, “seven” because there may be 12, or even just 1
CPU on that machine. We would need to know the number of CPU’s at runtime. Fortunately,
there is a way to find out this number during runtime.
Intel, AMD, z/Architecture and other architectures provide a way to get all kinds of information
on CPU’s. Not just the number of CPU’s, but whether or not they are multi-threaded CPU’s. A
dual core CPU, for example can allow 2 threads to run on that CPU at virtually the same time.
The term some architectures use is “Hyperthreaded processors.”
If we want to use a platform independent way to discover this information. We can’t use
assembly language + associated architecture.
a. Using the C++11 standard: if available, this api uses both the number of CPU cores
and hyper-threading units
#include <thread>
unsigned int nthreads = std::thread::hardware_concurrency();
b. On windows:
SYSTEM_INFO sysinfo;
GetSystemInfo( &sysinfo );
numCPU = sysinfo.dwNumberOfProcessors;
c. Using java:
import java.lang.Runtime;
int numberOfCPUs;
numberOfCPUs = Runtime.getRuntime().availableProcessors();
d. On a linux command line (search for processor in cpuinfo and pipe
the results to the wc command which will count the grep results:
grep processor /proc/cpuinfo | wc -l
2. “Accounting”, or data collection for thread pools. We need to “extend” the
ThreadPoolExecutor() class.
Most systems and even api’s provide hooks, or user exits that allow coders to supply any
alternative action, but usually accounting and debugging information. A second look at the
javadocs for ThreadPoolExecutor()
a. The constructor:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
Creates a new ThreadPoolExecutor with the given initial parameters and default
thread factory and rejected execution handler. It may be more convenient to use
one of the Executors factory methods instead of this general purpose constructor.
Parameters:
corePoolSize - the number of threads to keep in the pool, even if they are
idlemaximumPoolSize - the maximum number of threads to allow in the pool
keepAliveTime - when the number of threads is greater than the core, this is the
maximum time that excess idle threads will wait for new tasks before terminating.
unit - the time unit for the keepAliveTime argument
workQueue - the queue to use for holding tasks before they are executed. This
queue will hold only the Runnable tasks submitted by the execute method.
Throws:
IllegalArgumentException - if one of the following holds:
corePoolSize < 0
keepAliveTime < 0
maximumPoolSize <= 0
maximumPoolSize < corePoolSize
NullPointerException - if workQueue
is null
b. Hook methods
This class provides protected overridable beforeExecute(Thread, Runnable)
and afterExecute(Runnable, Throwable) methods that are called before and
after execution of each task. These can be used to manipulate the execution
environment; for example, reinitializing ThreadLocals, gathering statistics, or
adding log entries. Additionally, method terminated() can be overridden to
perform any special processing that needs to be done once the Executor has fully
terminated.
i. protected void beforeExecute(Thread t,
Runnable r)
Method invoked prior to executing the given Runnable in the given thread.
This method is invoked by thread t that will execute task r, This
implementation does nothing, but may be customized in subclasses.
ii. protected void afterExecute(Runnable r,
Throwable t)
Method invoked upon completion of execution of the given Runnable.
This method is invoked by the thread that executed the task. If non-null,
the Throwable (i.e. “t”) is the uncaught RuntimeException or Error that
caused execution to terminate abruptly.
iii. protected void terminated()
Method invoked when the Executor has terminated. Default
implementation does nothing.
3. Atomicity revisited: atomicity in java! We saw how to implement atomicity, using assembly
language and an architecture capability to provide an uninterruptable instruction (i.e. atomicity)
in test and set as well as _______ and ____ instructions. They are put to good using in, for
example, the AtomicLong class:
public class AtomicLong
extends Number
implements Serializable
A long value that may be updated atomically. See the
java.util.concurrent.atomic package specification for description of the
properties of atomic variables. An AtomicLong is used in applications such as
atomically incremented sequence numbers, and cannot be used as a replacement
for a Long. However, this class does extend Number to allow uniform access by
tools and utilities that deal with numerically-based classes.
There are a huge number of getter and setter methods. The sample code in the lab is using get()
as well as addAndGet(). For example, the Javadoc shows:
public final long addAndGet(long delta)
Atomically adds the given value to the current value.
Parameters:
delta - the value to add
Returns:
the updated value
4. In virtually all of the code examples and code assignments, we saw that accessing shared
(mutable) data requires some kind of synchronization between threads. The best way to avoid
this requirement: don’t share the data! Another good way: use just one thread! Seriously; if data
is needed/used by just one thread, no synchronization is necessary. “Java Concurrency in
Practice,” by Brian Goetz, defines this as idea as thread confinement.
a. Another way to provide thread confinement is through the use of thread local storage.
The “ThreadLocal storage allows programmers to associate a per-thread value with a
value-holding object and provides getters/setters that maintain a separate copy of the
value for each thread that needs it. A getter method returns the most recent value
passed to set from the currently executing thread. What? Let’s take a look at a picture:
Threads: t1
startTime
t2
startTime
...
tn
startTime
jjj
“Thread local” storage – a separate copy for each thread in one java program
5. We can write data to a file. We can also log data to a log. An instance of the log classcan send
that log to a file or to the output console.
a. An example of a log file (yes it is an xml file):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2015-11-15T13:40:41</date>
<millis>1447612841407</millis>
<sequence>0</sequence>
<logger></logger>
<level>INFO</level>
<class>java.util.logging.LogManager$RootLogger</class>
<method>log</method>
<thread>18</thread>
<message>Terminate: average time = 1002406260ns</message>
</record>
</log>
b. The log can also be redirected to your favorite IDE’s output console (or any command
line interface.) In fact, this is how exceptions are handled in the netbeans IDE;
exceptions are logged and sent to the output console.
A copy/paste from one of the runs of the sample stats application:
completed task: 6
Nov 15, 2015 1:42:15 PM java.util.logging.LogManager$RootLogger log
INFO: Thread null: end java.util.concurrent.FutureTask@3a0afb78, elapsed time =
1000330751ns
Nov 15, 2015 1:42:15 PM java.util.logging.LogManager$RootLogger log
INFO: Thread null: end java.util.concurrent.FutureTask@2f17749c, elapsed time =
999723265ns
c. From the javadocs:
A Logger object is used to log messages for a specific system or application component. Loggers
are normally named. … [redacted]
Logger objects may be obtained by calls on one of the getLogger factory methods. These will
either create a new Logger or return a suitable existing Logger. … [redacted] This is an example
of “The Singleton”
Logging messages will be forwarded to registered Handler objects, which can forward the
messages to a variety of destinations, including consoles, files, OS logs, etc.
Each Logger has a "Level" associated with it. … [redacted]
The log level can be configured based on the properties from the logging configuration file, as
described in the description of the LogManager class. However it may also be dynamically
changed by calls on the Logger.setLevel method. If a logger's level is changed the change may
also affect child loggers, since any child logger that has null as its level will inherit its effective
level from its parent.
On each logging call the Logger initially performs a cheap check of the request level (e.g.,
SEVERE or FINE) against the effective log level of the logger. If the request level is lower than
the log level, the logging call returns immediately.
After passing this initial (cheap) test, the Logger will allocate a LogRecord to describe the
logging message. It will then call a Filter (if present) to do a more detailed check on whether the
record should be published. If that passes it will then publish the LogRecord to its output
Handlers.
Download