Research on the Defect of Java Thread Model

xiaoxiao2021-03-06  112

The thread model of the Java programming language may be the weakest part of this language. It is not suitable for actual complex programs, and it is not an object-oriented. This paper recommends major modifications and supplements to Java languages ​​to address these issues. The thread model of the Java language is the most difficult part of this language. Although the Java language itself supports thread programming is a good thing, it is too small to support the syntax and package of threads, and can only apply to a minor application environment. Most books on Java thread programming have pointed out the defects of the Java thread model, and provide a first aid kit (BAND-AID / Bond Creative Creative) class library. I call these classes as a first aid kit because the issues they can solve should be included by the Java language itself. In the long run, in syntax rather than the class library method, a more efficient code will be produced. This is because the compiler and Java virtual device (JVM) can optimize the program code together, and these optimizations are difficult or unable to implement the code in the class library. Allen Holub pointed out that in my "Taming Java Threads" (see Resources) and this article, I further recommend some modifications to the Java programming language itself so that it can truly solve these thread programming. The main difference between this article and my book is that I have made more thinking when writing this article, so it has improved the proposal in the book. These recommendations are just trying - just my own ideas for these issues, and achieve these ideas requires a lot of work and the evaluation of peers. But this is a beginning, I intentionally set up a special working group to solve these problems. If you are interested, please send e-mail to threading@holub.com. Once I really start, I will send you a notice. The suggestions proposed here are very bold. Some people recommend subtle and small amounts of modifications to Java Language Norms (JLS) (see Resources) to solve the current blurred JVM behavior, but I want to make more thorough improvements. In practical draft, many of my recommendations include introducing new keywords for this language. Although usually do not break through an existing code that is correct, if the language is not to remain unchanged, it must be able to introduce a new keyword. In order to make the introduced keyword do not conflict with the existing identifier, I will use one ($) character, and this character is illegal in the existing identifier. (For example, using $ TASK, not TASK). At this point, the command line switch of the compiler is required to provide support, which can use these keywords, rather than ignore the dollar symbol. Task's concept of Java thread model is that it is not object-oriented. Object-oriented (oo) designers do not consider problems at the angle of thread; they consider synchronous information asynchronous information (synchronous information is processed immediately - until the information processing is completed, return message handle; asynchronous information will be processed in the background Time - return message handle after the end of the information processing). The Toolkit.GetImage () method in the Java programming language is a good example of asynchronous information. The message handle of GetImage () will be returned immediately without having to wait until the entire image is retrieved by the background thread. This is an object-oriented (OO) processing method. However, as mentioned earlier, Java's thread model is non-objective. A Java programming language thread is actually just a run () process, which calls other processes. Here, there is no object, asynchronous or synchronization information, and other concepts. For this issue, a solution to the in-depth discussion in my book is to use an Active_Object.

The Active object is an object that can receive asynchronous requests, which is handled in a later manner within a period of time after receiving the request. In the Java programming language, a request can be packaged in an object. For example, you can transfer an instance implemented by the runnable interface to this Active object, the Run () method of the interface encapsulates the work that needs to be completed. This runnable object is discharged into the queue by this Active object. When the turn is executed, the Active object uses a background thread to execute it. Asynchronous information running on an Active object is actually synchronized because they are removed from the queue in order by a single service thread. Therefore, use an Active object to eliminate most synchronization problems in a more process-friendly model. In a sense, the entire SWING / AWT subsystem of the Java programming language is an Active object. The only secure way to transfer a message to a Swing queue is to call a method similar to swingutilities.invokelater () so that a runnable object is sent on the Swing event queue. When the turn is executed, the Swing event handles thread will Will deal with it. Then my first suggestion is to add a Task concept to the Java programming language to integrate the Active object into the language. (Task concept is borrowed from Intel's RMX operating system and ADA programming language. Most real-time operating systems support similar concepts.) A task has a built-in Active object distributor and automatically manages those processed information. All mechanisms. Define a task and define a class that is basically the same, only need to add an Asynchronous modifier to indicate these methods in the background before the method of the task is required to indicate the allocation of the ACTIVE object. All write requests are queued in the input queue of Active-Object with a DISPATCH () process. Any exception (Exception) that occurs when processing these asynchronous information in the background is processed by an Exception_Handler object, which is transmitted to the constructor of File_IO_TASK. This kind of class-based processing method is too complicated - for a simple operation, the code is too miscellaneous. After introducing the $ TASK and $ ASYNCHRONOUS keywords to the Java language, you can override the previous code below: Note that the asynchronous method does not specify the return value because its handle will return immediately, without waiting for the requested operation processing Rear. Therefore, there is no reasonable return value at this time. For derived models, $ TASK keywords, and Class are as synonymous: $ TASK can implement interfaces, inherit, and other tasks of inheritance. The method labeled asynchronous keyword is processed by $ TASK in the background. Other methods will be run synchronously, just like in the class. The $ TASK keyword can be modified with an optional $ error clause (as shown above), indicating that any exception that cannot be captured by the asynchronous method itself will have a default handler. I use $ to represent the thrown anomalies. If you do not specify a $ error clause, a reasonable error message will be printed (it is probably the stack tracking information). Note that in order to ensure the safety of threads, the parameters of the asynchronous method must be constant (immutable). The runtime system should ensure that this invariance is guaranteed by the semantics (simple replication is usually not enough). All Task objects must support some pseudo information (Pseudo-Message).

In addition to common modifiers (public, etc.), Task keying should also accept a $ POOLED (N) modifier, which causes Task to use a thread pool instead of using a single thread to run asynchronous requests. n Specify the size of the required thread pool; if necessary, this thread pool can be increased, but it should be reduced to the original size when it is no longer needed. Pseudo-Field $ pool_size Returns the original n parameter value specified in $ POOLED (N). In Chapter 8 of Taming Java Threads, I give a server-side Socket handler as an example of a thread pool. It is a good example of the task of using the thread pool. Its basic idea is to generate an independent object, and its task is to monitor the Socket of a server-side. Whenever a client is connected to the server, the server-side objects capture a pre-created sleep thread from the pool and set this thread to serve the client. The Socket server produces an additional customer service thread, but these additional threads will be deleted when the connection is closed. Socket_server object uses a separate background threading process asynchronous listen () request, which encapsulates the "Accept" cycle of Socket. When each client is connected, the listen () requests a client_handler to handle the request by calling handle (). Each handle () request is executed in their own thread (because this is a $ POOLED task). Note that each asynchronous message transmitted to $ Pooled $ Task is actually handled by their own thread. Typically, since a $ POOLED $ TASK is used to implement a self-operative operation; so for a potential synchronization problem associated with access status variables, the best solution is to use this this to use this to use this. Unique copy. That is to say, when sending an asynchronous request to a $ POOLED $ TASK, a clone () operation will be executed, and the THIS pointer of this method will point to the object. Communication between threads can be implemented by synchronous access to the STATICI. Improve Synchronized Although $ Task eliminates the requirements of synchronous operations, not all multi-threaded systems are implemented with tasks. Therefore, it is also necessary to improve the existing thread module. Synchronized keyword has the following shortcomings: You cannot specify a timeout value. Unable to break a thread that is waiting for the request lock. Multiple locks cannot be requested safely. (Multiple locks can only be obtained in order.) Solving these issues is to extend the synchronized syntax, allowing it to support multiple parameters and accept a timeout description (specified in the parentheses below). Here is the syntax I want:

Synchronized (X && Y && Z) is locked by x, y, and z objects. Synchronized (x || y || z) gets the lock of the X, Y or Z object. Synchronized ((x && y) || z) Some of the extensions of the previous code. Synchronized (...) [1000] sets 1 second timeout to get a lock. Synchronized [1000] f () {...} gets this lock in the F () function, but there can be 1 second timeout. Timeoutexception is the RuntimeException derived class, which is thrown after waiting for the timeout. Timeout is needed, but it is not enough to make the code strong. You also need to have the ability to wait for the waiting lock from the outside. Therefore, after transmitting an interrupt () method to a wait-locking thread, this method should throw a SynchronizationException object and interrupt the waiting thread. This exception should be a derived class of RuntimeException, so you don't have to deal with it. The main problem with the recommended changes to Synchronized syntax is that they need to be modified on binary code levels. This code currently implements Synchronized using access to monitoring (ENTER-MONITOR) and exit-monitor instructions. These instructions do not have parameters, so they need to extend the definition of binary code to support multiple locked requests. But this modification will not be easier than modifying the Java virtual machine in Java 2, but it is a downwardly compatible Java code. Another solving problem is the most common deadlock situation, in which case two threads are waiting for the other party to complete a certain action. Imagine a thread to call A (), but after obtaining Lock2, it is deprived before obtaining LOCK2. The second thread enters operation, calls B (), obtained LOCK2, but since the first thread occupies Lock1, it cannot obtain LOCK1, so it is then waiting. Status. At this point, the first thread was awakened, it tried to obtain LOCK2, but because of the second thread occupied, it was not available. The deadlock appears. The compiler (or virtual machine) will rearrange the order of the request lock, so that the LOCK1 is always first obtained, which eliminates the deadlock. However, this method is not necessarily successful for multi-threads, so it has to provide some methods to automatically break the dead lock. A simple approach is to release the obtained locks often waiting for the second lock. If each program of the waiting lock uses a different timeout value, you can break the deadlock and one of the threads can run. I recommend using the following syntax to replace the previous code: SYNCHRONIZED statement will wait forever, but it often gives up the locked lock to break the potential deadlock. Ideally, the timeout value of each repeated wait is a random value than the previous phase. There are also some questions in Wait () and Notify () Wait () / Notify () system: WAIT () is not detected whether it is returned to the timeout. The traditional condition variable cannot be used to implement a "signal" state. Too prone to nested monitoring (MONITOR) locks. Oversessure detection issues can be resolved by redefining Wait () to return a Boolean variable (instead of void). A TRUE return value indicates a normal return, and the false indicates that the timeout returns. The concept of state-based condition variable is important. If this variable is set to a FALSE state, the wait thread will be blocked until this variable enters the TRUE state; any wait thread waiting for the condition variable waiting for TRUE will be released. (In this case, Wait () call does not block.).

Nested monitoring locks is very troublesome, I don't have a simple solution. Nested monitoring lock is a deadlock form that this nested monitor blockade occurs when a lock has a thread that does not release the lock before he is suspended. In this example, two locks are involved in GET () and PUT (): one on the Stack object and the other on the LinkedList object. Let's consider that when a thread is trying to call a vacant POP () operation. This thread gets these two locks and then calls Wait () release the lock on the Stack object, but does not release the lock on the List. If the second thread attempts to press an object in the stack, it will always hang on the Synchronized (List) statement and will never be pressing an object. Since the first thread is waiting is a non-empty stack, a deadlock occurs. That is to say, the first thread will never return from Wait () because it causes the second thread to run to the notify () statement because it occupies the lock. In this example, there are many obvious ways to solve the problem: for example, synchronization is used for any method. But in the real world, the solution is usually not so simple. A possible way to release all locks acquired when Wait () releases the front thread, and then re-press the original acquisition order when the wait condition is satisfied. However, I can imagine that the code that uses this way is simply unable to understand, so I think it is not a truly possible way. If you have a good way, please send me E-mail. I also hoped to wait until the next day of complex conditions. For example: where A, B and C are any object. Modifying the Thread class also supports the ability to preemptively and collaborative threads in some server applications, especially if they want to achieve the highest performance. I think the Java programming language is too far from the simplified thread model, and the Java programming language should support POSIX / Solaris "Green Thread" and "Lightweight Process" concept (in "(Taming Java Threads" The first chapter discussed). That is to say, some Java virtual machines (such as Java virtual machines on NT) should be in its internal simulation collaborative process, other Java virtual machines should simulate the progress thread. And to Java virtual It is easy to join these extensions. A Java's Thread should always be seized. This means that a Java programming language thread should work just like Solaris. Runnable interface can be used to define a Solaris style Green thread ", this thread must be able to transfer control to other green threads running in the same lightweight process. Effectively generate a green thread for Runnable objects, and bind it to the lightning process represented by the Thread object. This implementation is transparent to existing code because its validity is exactly the same. Take the runnable object to become a green thread, use this method, just pass several Runnable objects to the constructor of Thread, You can extend the existing syntax of the Java programming language to support multiple green threads in a single lightweight thread. (Green threads can be collaborated with each other, but they can be running on the green process on other lightweight processes (THRead objects) ( Runnable objects are preempt.).), For example, the following code creates a green thread for each Runnable object, which shares the lightning process represented by the Thread object.

Existing coverage Thread objects and implement Run () habits continue to be valid, but it should be mapped to a green thread that is bound to a generic process. (Default Run () method in the thread () class will effectively create the second RunNable object inside.) Collaboration between threads should add more features to support inter-threads. Currently, the PipedInputStream and the PipedOutputStream class can be used for this purpose. But for most applications, they are too weak. I recommend adding the following functions to the Thread class: add a wait_for_start () method, which is usually blocked until a thread's Run () method is started. (If the waiting thread is released before the RUN is called, this is not a problem). With this method, a thread can create one or more auxiliary threads and ensure that these secondary threads are running before creating a thread continues to perform operations. (To the Object class) Add $ Send (Object O) and Object = $ receive () method, which will use an internal blocking queue to transfer objects between threads. The blocking team should be automatically created as a by-product called by the first $ send (). $ Send () call will join the object to the queue. $ Receive () call usually in the blocking state until an object is added to the queue, then it returns this object. The variables in this method should support the settings of the team and the outgoing operation timeout: $ Send (Object O, Long Timeout) and $ Receive (Long Timeout). The concept of reading and writing locks for the read and write lock should be built into the Java programming language. The reader lock has a detailed discussion in "Taming Java Threads" (and elsewhere), in summary: A read-write lock supports multiple threads to access an object at the same time, but only one thread can modify this object at the same time, and You cannot modify when access is performed. For an object, you should only support multiple threads to enter $ Reading block when there is no thread in the $ Writing block. When reading a read operation, a thread that tries to enter the $ WRITI block will be blocked until the reading thread exits $ reading block. When there are other threads at a $ Writing block, threads attempt to enter $ Reading or $ Writing block are blocked until this write thread exits $ WRITI block. If the read and write thread is waiting, the reading thread will be performed first. However, this default method can be changed using the $ Writer_Priority property to modify the definition of the class. The object created by the access section should be illegal current cases, the JLS allows the access part to create objects. For example, the thread created in a constructor can access the object being created, which makes this object not completely created. Setting the x -1 thread can be simultaneously performed and the thread set X is 0. Therefore, the value of X cannot be predicted at this time. One solution to this problem is that the thread created in this constructor is not returned before the constructor is not returned, and the RUN () method is to be prohibited by making its priority than calling the New NEW. That is to say, the start () request must be postponed before the constructor returns. In addition, Java programming languages ​​should allow synchronization of constructor. In other words, the following code (in the current case is illegal) work like expected: I think the first method is more concise than the second, but it is more difficult. The Volatile keyword should be like the expected work JLS requires the request for the Volatile action.

Most Java virtual machines simply ignore this part of this part, this should not be. In the case of a multi-processor, many hosts have problems, but it should be solved by JLS. If you are interested in this, the Bill Pugh of the University of Maryland is committed to this work (see Referring). Access issues If there is a lack of good access control, it will make thread programming very difficult. In most cases, if the thread can only be called from the synchronization subsystem, there is no need to consider the thread security (Threadsafe) problem. I recommend the following restrictions on the Java programming language; you should use the package keyword to limit the package access. I think the existence of default behavior is a flaw of any computer language. I feel confused in this default permission (and this default is "package" level rather than "private ( private) "). In other aspects, Java programming languages ​​do not provide equivalent default keywords. Although the definite word using explicit packages will destroy the existing code, it will make the code's readability and eliminate potential errors for the entire class (for example, if the access is ignored, not being ignored instead of being Not intentionally ignored). Retroducing Private Protected, its function should be like the current protected, but should not allow access to the package. Allow the Private Private Syntax Specify "Access to Implementation" for all external objects, even the current object is the same class. For "." The only reference (implicit or explicit) on the left should be this. Extend the grammar of the public to authorize it to enable specific class access. For example, the following code should allow the Fred class to call some_method (), but the object to other classes, this method should be private. This recommendation is different from the "Friend" mechanism of C . In the "Friend" mechanism, it authorizes all the private parts of another class. Here, I recommend a strict control of a limited method collection. With this method, a class can define an interface for another class, and this interface is invisible to the rest of the system. All domain definitions should be private unless the domain reference is an object or static factory basic type of IMMutable. For a class of direct access violates two basic rules for OO design: abstraction and package. From the thread perspective, the direct access field is allowed to make it easier to simultaneously access it. Add $ Property keyword. Objects with this keyword can be accessed by a "bean box" application, which uses the Introspection API defined in the Class class, otherwise the private private is the same. The $ proty property can be used in domains and methods so that the existing JavaBean getter / setter method can be easily defined as attributes. Immutability Due to the need to synchronize the invariant object, the constant concept (the value of an object is not changed after creating) in multi-thread conditions. In Java programming speech, there is two reasons for the realization of invariance: for a constant object, it can be accessed before it is not completely created. This access can generate incorrect values ​​for some domains. The definition of constant (all domains of classes is FINAL) is too loose. For objects specified by Final reference, although reference itself cannot be changed, the object itself can change the state.

The first question can be resolved, and the thread is not allowed to start executing in the constructor (or the start request cannot be performed before the constructor returns). For the second question, this problem can be resolved by defining the final modifier to a constant object. That is to say, for an object, only all the domains are final, and all reference objects are also Final, this object is really constant. In order not to break the existing code, this definition can be strengthened using the compiler, that is, only one class is explicitly unchanged, this class is invariant. With the $ IMMUTABLE modifier, the final modifier in the domain definition is optional. Finally, when an internal class (Inner Class) is used, an error in the Java compiler makes it unconservative objects. This error message will occur even in each constructor both in each constructor. Since the introduction of the internal classes in version 1.1, there is this error in the compiler. In this release (three years later), this error still exists. Now, it is time to correct this error. For instance-level access to the class level In addition to access, there is a problem, ie the class (static) method and instance (non-static) method can access the class (static) domain directly. This access is very dangerous because synchronization of the instance method does not acquire the class lock, so a SYNCHRONIZED Static method and a synchronized method or the domain of the class. An obvious way to correct this problem is that only the Static access method can be accessed only in an instance method to access the non-invariant Static domain. Of course, this requirement requires compiler and runtime check. Since F () and g () can run in parallel, they can simultaneously change the value of X (resulting in an unprofit result). Keep in mind that there are two locks here: the Static method requires the lock that belongs to the Class object, rather than static methods require locks that belong to such instances. Or, the compiler should obtain the read / write lock: another method is (this is also an ideal method) - the compiler should automatically use a read / write lock to synchronize access non-constant Static domain, so The programmer doesn't have to worry about this problem. After all the non-background threads are terminated, the background thread is suddenly ended. Some global resources are created in the post (such as a database connection or a temporary file), while these resources are not turned off or deleted at the end of the background thread. For this issue, I recommend setting the rules to make the Java virtual machine do not close the application in the following cases: Any non-background thread is running, or if any background thread is executing a Synchronized method or a synchronized code block. The background thread can be turned off immediately after it performs the SYNCHRONIZED block or the synchronized method. Re-introducing Stop (), suspend () and resume () keywords may not be feasible, but I hope not to abolish Stop () (in Thread and ThreadGroup). However, I will change the semantics of STOP () so that it does not destroy the existing code when calling it. However, about STOP (), keep in mind that STOP () will release all locks after the thread is terminated, which may potentially enable threads working on this object into a state of unstable (local modification).

Since the stopped thread has released all the locks on this object, these objects cannot be accessed. For this problem, you can redefine the behavior of Stop () so that the thread is only terminated immediately when no lock is occupied. If it occupies a lock, I suggest that it will terminate it after this thread releases the last lock. This behavior can be implemented using one and an abnormal mechanism. The stop thread should set a sign and test this flag immediately when all synchronization blocks are exited. If this flag is set, an implicit exception is thrown, but this exception should no longer be captured and will not produce any output when the thread ends. Note that Microsoft's NT operating system does not have a high stop of an external indication (ABRUPT). (It does not notify the STOP message to dynamically connect libraries, so it may cause system-level resource vulnerabilities.) This is what I recommend using similar abnormal methods simply cause Run () returns. The actual problem brings to this and abnormal similar processing method is that you must insert the code after each Synchronized block to test the "stopped" flag. And this additional code will reduce system performance and increase the length of code. Another way I think is to stop STOP () to delay "" in this case, when the next call wait () or yield () is terminated. I also want to join a isStopped () and stopped () method to Thread (this point, Thread will work like ISINTERRUPTED () and Interrupted (), but will detect the state of "stop-requested". This method is not common to the first one, but it is feasible and does not produce overload. Suspend () and resume () should be put back into the Java programming language, they are very useful, I don't want to be treated as a kindergarten. Since they may have potential dangers (when they are hanging, a thread can take a lock) and it is not reasonable. Please let me to decide whether to use them. If the received thread is being locked, Sun should use them as a run-time exception to call Suspend (); or a better way to delay the actual suspend process until the thread releases all Lock. The blocked I / O should be able to interrupt any blocked operations correctly, rather than only let them wait () and SLEEP (). I discussed this issue in the Socket section in Chapter 2 of Taming Java Threads. But now, for an I / O operation on a blocking socket, the only way to interrupt it is to close this socket, and there is no way to interrupt a blocking file I / O operation. For example, once a read request is started and enters the blocking state, the thread has been blocked by the blocking state unless it actually reads something. Do not interrupt read operations both the clearance file handle. Also, the program should support the timing of I / O operation. All objects that may have blocked operations (such as InputStream objects) should also support this method. This is equivalent to the Socket class's setsotimeout (time) method. Similarly, it should be supported to transfer the timeout as a parameter to the blocking call. ThreadGroup class ThreadGroup should implement all methods of changing thread state in Thread. I especially wanting it to implement the Join () method so I can wait for the termination of all threads in the group. Summarizing the above is my suggestion. Just like I said in the title, if I am a king ... (). I hope these changes (or other equivalents) will eventually be introduced into the Java language.

转载请注明原文地址:https://www.9cbs.com/read-100857.html

New Post(0)