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.