Original address: http://www.ibm.com/developerworks/cn/java/j-lo-processthread/#icomments processes and Threads in Java
Processes and threads undoubtedly occupy an extremely important position in program development, and the Java language is bound to further encapsulate the relevant functions provided by the operating system in order to provide a unified, platform-independent programming interface for processes and threads. This article focuses on the related wrapper classes for processes and threads in Java, revealing how to create Java processes and threads, how the Java wrapper classes and actual system local processes and threads correspond, and some of the limitations of using Java processes and threads.
Overview
Process and thread, in essence, is the operating system scheduling unit, can be regarded as an operating system "resources." Java, as a platform-independent programming language, is bound to further encapsulate the functionality provided by the underlying (operating system), with platform-agnostic programming interfaces for programmers, and processes and threads as part of the core concept of the operating system. In the Java language, the encapsulation of processes and threads provides some classes related to process and thread, respectively. This article first briefly describes how to use these classes to create processes and threads, and then focuses on how these classes correspond to the local process line threads of the operating system, gives a summary of the Java Virtual machine's implementation of these encapsulation classes, and also hides some of the underlying concepts and operability due to the Java encapsulation. The Java process thread and the local process thread have made some simple comparisons, listing some of the limitations of using Java processes, threads, and issues that require attention.
How to build Java processes
In the JDK, the class that is directly related to the process is Java.lang.Process, which is an abstract class. A Processimpl class that implements the abstract class is also provided in the JDK, and if the user creates a process, it is bound to be accompanied by a new Processimpl instance. Also closely related to process creation is the Processbuilder, which began to appear in JDK1.5, providing a convenient way to configure the environment for the new process, the directory, and whether to merge the error stream and output stream, relative to the processes class.
Both the Java.lang.Runtime.exec method and the Java.lang.ProcessBuilder.start method can create a local process and then return a Java.lang.Process reference that represents the process.
Runtime.exec method to establish a local process
In JDK1.5, the method can accept 6 different forms of parameter passing in.
Process exec (string command) process exec (string [ ] cmdarray) process exec (string [] Cmdarrag, String [] envp)
process exec (string [] Cmdarrag, String [] envp, File dir) process exec (string cmd, string [] envp) process exec (String command, string [] envp, File dir)
Their main differences are in the form of incoming command parameters, the environment variables provided, and the definition of the execution directory.
Processbuilder.start method to establish a local process
If you want to use the current directory and environment variables in the newly created process, you do not need any configuration, pass in the command line and arguments directly into Processbuilder, and then call the Start method to get a reference to the process.
Process p = new Processbuilder ("command", "Param"). Start ();
You can also configure environment variables and working directories before you create a process.
Processbuilder PB = new Processbuilder ("command", "param1", "param2"); map<string, string> env = Pb.environment (); Env.put ("VAR", "Value"); Pb.directory ("Dir"); Process p = Pb.start ();
The Processbuilder properties that can be preconfigured are the greatest benefits of creating processes through Processbuilder. And you can change the properties of the PB variables in the code as needed in later use. If subsequent code modifies its properties, it affects the process created with the Start method after the modification, and has no effect on the process instance that was created before the modification.
JVM-to-process implementation
In the JDK code, only the Processimpl class is provided to implement the Process abstraction class. It references the native create, close, waitfor, Destory and Exitvalue methods. In Java, the native method is a native method that relies on the operating system platform, and its implementation is implemented in a similar low-level language such as C + +. We can find the corresponding local method in the JVM's source code, and then analyze it. The JVM's implementation of the process is relatively straightforward, taking the JVM under Windows as an example. In the JVM, pass in Java the arguments passed in when the method is called to the operating system corresponding to the method to implement the corresponding function. As shown in table 1
Table 1. How the native method in the JDK corresponds to the Windows API
| the name of the native method called in the JDK |
the Windows API that corresponds to the call |
| Create |
Createprocess,createpipe |
| Close |
CloseHandle |
| waitfor |
WaitForMultipleObjects |
| Destroy |
TerminateProcess |
| Exitvalue |
GetExitCodeProcess |
Taking the Create method as an example, let's look at how it is connected to the system API.
In the Processimple class, there is a native create method with the following parameters:
Private native long Create (string cmdstr, String envblock, string dir, Boolean redirecterrorstream, FileDescriptor in _FD, filedescriptor out_fd, FileDescriptor err_fd) throws IOException;
The corresponding local method in the JVM is shown in code Listing 1.
Listing 1
Jniexport jlong jnicall java_java_lang_processimpl_create (jnienv *env, jobject process, jstring cmd, jstring envBlock, Jstring dir, Jboolean Redirecterrorstream, Jobject in_fd, Jobject out_fd, Jobject err_fd) {/* Set internal variable value */... /* Build input, output, and error stream pipeline */if (!) CreatePipe (&inread, &inwrite, &sa, pipe_size) && createpipe (&outread, &outwrite, &sa, pipe_size) && createpipe (&errread, &errwrite, &sa, pipe_size)) {throwioexception (env, " CreatePipe failed "); Goto Catch; }/* To convert the parameter format */pcmd = (LPTSTR) jnu_getstringplatformchars (env, CMD, NULL); .../* Call the system-provided method to establish a Windows process */ret = CreateProcess (0,/* executable name */pcmd,/* command Line */0,/* Process security attribute */0,/* Thread security attribute */TRUE,/* inh Erits system handles */Processflag,/* Selected based on EXE type */penvblock,/* Environment block */Pdir, /* CHAnge to the new current directory */&si,/* (in) startup information */&PI); /* (out) process information */.../* Get a handle to the new process */ret = (Jlong) pi.hprocess; .../* finally returns the handle */return RET; }
You can see that when you create a process, you invoke the CreatePipe method provided by Windows to establish input, output, and error pipelines, while converting the user's passed in Java parameters to the C language format recognized by the operating system, and then invoking the way Windows provides to create a system process , a process is created and the corresponding handle of the process is saved in the JAVA virtual machine, and then returned to the Processimpl class, but the class hides the return handle. It is also a feature of the Java cross-platform that, as far as possible, the JVM encapsulates and hides the implementation details associated with the operating system.
Similarly, after a user calls the close, waitfor, Destory, and Exitvalue methods, the JVM first obtains a handle to the process in the operating system that was previously saved, and then operates on the process by invoking an interface provided by the operating system. This is the way to implement operations on the process.
Other platforms are implemented in a similar way, unlike the APIs that invoke the corresponding platform.
Java processes and operating system processes
Through the above analysis of the Java process, in fact, it is implemented is to create a process of the operating system, that is, each JVM created by the process corresponds to a process in the operating system. However, Java in order to give users a better and more convenient use, to the user to block some platform-related information, which users need to use the time, brought a little inconvenience.
When you create a system process using C + +, you can obtain the PID value of the process, which can be used to operate the corresponding process directly through the PID. In JAVA, however, the user can only operate through the reference to the instance, and when the reference is lost or unreachable, it is not possible to know any information about the process.
Of course, there are some things to be aware of when using the Java process:
- Java provides a very limited amount of input and output pipe capacity, which can cause a process to hang or even cause a deadlock if not read in time.
- When creating a process to execute system commands under Windows, such as: dir, copy, and so on. You need to run the command interpreter for Windows, Command.exe/cmd.exe, which depends on the version of Windows so that you can run system commands.
- For pipelines in the Shell ' | ' Command, redirection command under each platform ' > ' cannot be implemented directly through command parameters, but requires some processing in Java code, such as defining a new stream to store standard output, and so on.
In summary, the process of the operating system is encapsulated in Java, which masks information about the operating system process. At the same time, use caution when using Java to provide a creation process to run local commands.
In general, the use of processes is to perform a task, while the modern operating system for the execution of the task of computing resources are generally configured to the thread as the object (the early Unix-like system because the thread is not supported, so the process is also a scheduling unit, but that is a relatively lightweight process, not in-depth discussion here). Creates a process in which the operating system actually creates the appropriate thread to run a series of instructions. In particular, when a task is rather large and complex, it may be necessary to create multiple threads to implement logically concurrently, and the threads are more visible. Therefore, it is necessary to understand the threads in Java in order to avoid any possible problems. What follows in this article is how the Java thread is created and how it relates to the operating system thread.
Java Methods for creating threads
In fact, the most important thing to create a thread is to provide the thread function (the callback function), which acts as the entry function for the newly created thread, implementing its desired function. Java provides two ways to create a thread:
- Inherit the Thread class
Class MyThread extends thread{public void Run () { System.out.println ("My Thread is started."); }
Implement the Run method of the inheriting class, and then you can create the object of the subclass, and call the Start method to create a new thread:
MyThread MyThread = new MyThread (); Mythread.start ();
- Implementing the Runnable Interface
Class Myrunnable implements runnable{public void Run () { System.out.println ("My Runnable is invoked."); }
An object of a class that implements the Runnable interface can be passed as a parameter to the created thread object, and the code in the Run method can be run in a new thread by calling the Thread#start method.
Thread myThread = new Thread (new myrunnable ()); Mythread.start ();
As you can see, no matter which method you use, you are actually implementing a run method. The method is essentially the previous callback method. The newly created thread by the Start method calls this method to execute the required code. As you can see from the back, the Run method is not a real thread function, just a Java method called by a thread function, and no other Java method is fundamentally different.
Implementation of Java Threads
Conceptually, the creation of a Java thread essentially corresponds to the creation of a local thread (native thread), which corresponds to one by one. The problem is that the local thread should be doing local code, and the Java thread provides a Java method that compiles Java bytecode, so it is conceivable that the Java thread actually provides a uniform thread function that invokes the Java threading method through a Java virtual machine. This is done through a Java local method call.
The following is an example of the Thread#start method:
Public synchronized void Start () { ... Start0 (); ... }
You can see that it actually calls the local method Start0, which declares the following:
Private native void Start0 ();
The thread class has a registernatives local method, and the main function of this method is to register some local methods for use by the thread class, such as Start0 (), Stop0 (), and so on, so to speak, all local methods that operate the local thread are registered by it. This method is placed in a static statement block, which indicates that when the class is loaded into the JVM, it is called and the corresponding local method is registered.
private static native void Registernatives (); static{ registernatives (); }
The local method registernatives is defined in the Thread.c file. THREAD.C is a very small file that defines common data and operations about threads that are used by each operating system platform, as shown in Listing 2.
Listing 2
Jniexport void Jnicall java_java_lang_thread_registernatives (jnienv *env, Jclass cls) {(*env)->registernatives (en V, CLS, Methods, Array_length (methods)); } static Jninativemethod methods[] = {{"Start0", "() v", (void *) &jvm_startthread}, {"Stop0", "(" OBJ ") v", (v OID *) &jvm_stopthread}, {"IsAlive", "() Z", (void *) &jvm_isthreadalive}, {"Suspend0", "() V", (void *) &jvm_ SuspendThread}, {"RESUME0", "() v", (void *) &jvm_resumethread}, {"SetPriority0", "(I) v", (void *) &jvm_ SetThreadPriority}, {"Yield", "() v", (void *) &jvm_yield}, {"Sleep", "(J) V", (void *) &jvm_sleep}, {" CurrentThread "," () "THD, (void *) &jvm_currentthread}, {" Countstackframes "," () I ", (void *) &jvm_ Countstackframes}, {"Interrupt0", "() V", (void *) &jvm_interrupt}, {"Isinterrupted", "(z) z", (void *) &jvm_ Isinterrupted}, {"Holdslock", "(" OBJ ") Z", (void *) &jvm_holdslock}, {"Getthreads", "() [" THD, (void *) &jvm_ Getallthreads}, {"Dumpthreads", "([" THD ") [[" STE, (void *) &jvm_dumpthreads},};
In this way, it is easy to see how the Java thread calls start, and actually calls the Jvm_startthread method, which is the logic of this method. In fact, what we need is (or Java performance behavior) that the method eventually calls the Java thread's Run method, which is indeed the case. In Jvm.cpp, there is the following code snippet:
Jvm_entry (void, jvm_startthread (jnienv* env, Jobject jthread)) ... native_thread = new Javathread (&thread_entry, SZ); ...
here Jvm_entry is a macro that defines the Jvm_startthread function, and you can see that a real platform-related local thread is created inside the function, and its thread function is Thread_entry, as shown in Listing 3.
Listing 3
static void Thread_entry (javathread* thread, TRAPS) { Handlemark HM (thread); Handle obj (THREAD, thread->threadobj ()); Javavalue result (t_void); Javacalls::call_virtual (&result,obj, klasshandle (Thread,systemdictionary::thread_klass ()), Vmsymbolhandles::run_method_name (), vmsymbolhandles::void_method_signature (), THREAD); }
You can see that the Vmsymbolhandles::run_method_name method is called, which is defined in VMSYMBOLS.HPP with a macro:
Class Vmsymbolhandles:allstatic {... template (Run_method_name, "Run") ...}
As to how run_method_name is defined, this article does not repeat itself because of the cumbersome code details involved. Interested readers can view the source code of the JVM themselves.
Figure 1. Java Thread Creation Call graph
In summary, the Java thread creation call process is shown in Figure 1, first, the Java thread's Start method creates a local thread (by calling Jvm_startthread), the thread function of which is defined in Jvm.cpp thread_entry, which is then Call the Run method further. You can see that the Java thread's Run method and the normal method do not have the essential difference, the direct call to the Run method does not error, but it is executed on the current thread, and does not create a new thread.
Java Threads and Operating system threads
From the above we know that Java threads are built on the local thread of the system and are another layer of encapsulation, which has the following limitations for the interfaces provided by the Java Developer:
Thread return value
Java does not provide a way to get the exit return value of a thread. In fact, a thread can have an exit return value, which is typically stored in an online programming structure (TCB) by the operating system, and the caller can detect the value to determine whether the thread exits gracefully or terminates abnormally.
Synchronization of Threads
Java provides method Thread#join () to wait for a thread to end, which is generally sufficient, but one possible scenario is that it is not possible to wait on multiple threads (such as any one thread to end or all threads to end), and it is not feasible to loop through the Join method of each thread. This can lead to very strange synchronization problems.
The ID of the thread
The Java-provided method Thread#getid () returns a simple count ID that has nothing to do with the operating system thread's ID.
Thread Run Time statistics
Java does not provide a way to obtain statistical results of the elapsed time of a piece of code in a thread. Although you can use the timing method to achieve (get run start and end time, and then subtract), but because of the multi-threaded scheduling method, unable to get the actual CPU time used by the thread, and therefore must be inaccurate.
Summarize
Through the analysis of Java processes and threads, we can see that Java encapsulates the two operating system "resources", allowing developers to focus on how to use the two "resources" without having to worry too much about the details. This kind of package reduces the developer's work complexity, improves the working efficiency, on the other hand, because the encapsulation masks some features of the operating system itself, there are some limitations when using Java process threads, which is an unavoidable problem in encapsulation. The evolution of language is the process of deciding what does not need, and it is believed that with the development of Java, the functional subset of encapsulation becomes more and more perfect.
Reprinted, IBM piece of article, about thread start method execution