Defect of Java thread

xiaoxiao2021-03-06  63

Allen Holub pointed out that 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. In my "Taming Java Threads" book and in this article, I further recommend some modifications to the Java programming language itself so that it can truly solve these thread programming issues. 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 task is required. Please refer to the category based method in my book, then look at the following file_io class, which uses the Active_Object class discussed in "Taming Java Threads" to implement asynchronous write: all write requests are used with a DISPATCH () Process calls queued in the input queue of Active-Object. 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. When you want to write content to the file, the code is as follows: This class-based processing method, its main problem is too complex - 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), for example, in addition to common modifiers (public, etc.), Task keywords 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. Recommend the recommended syntax for the Socket server as follows: 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) to get the lock of the X, Y, Z object. 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 to implement SYNCHRONIZED. 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 an example of the following: Imagine a thread 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 following Synchronize-ON-MultiPle-Objects syntax can solve this problem: Compiler (or virtual machine) rearranges the order of request locks, making Lock1 always get first, which eliminates dead locks. 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. That is to say, you should take the following wait, not always waiting: If each program of the lock is used to use a different timeout value, you can break the deadlock and one of the threads can be 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.). With the following expansion of the syntax of Notify (), you can support this feature: 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.

Below is an example of this problem (or assuming), but the actual example is very much: In this case, two locks are involved in the GET () and PUT () operations: one on the Stack object, the other 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 transfer control to other green threads running in the same lightweight process. For example, the current syntax: can effectively generate a green thread for the Runnable object, and bind it to the Thread object Among the lightning processes of the representative. This implementation is transparent to existing code because its validity is exactly the same. Turn the runnable object to a green thread, use this method, just pass the constructor of Thread Several runnable objects can extend the existing syntax of the Java programming language to support multiple green threads in a single light thread. (Green threads can be collapsed together, but they can be run in other lightweight processes (Thread objects) The green process (Runnable object) seizes.), For example, the following code creates a green thread for each runnable object, which shares a lightweight process represented by the Thread object. Existing coverage (Override) Thread Objects and enable 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 inside 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. The syntax of the read and write lock can borrow the synchronized keyword: 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. Such as: The object created by the access section should be illegal current cases, JLS allows access to the object to be created. For example, the thread created in a constructor can access the object being created, which makes this object not completely created. The result of the following code cannot be determined: setting X is -1 thread can be simultaneously performed at the same time. 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. A significant change is: All domain definitions should be private unless the domain reference is an object or static final basic type of the 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. The method is as follows: After 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. When a class has an important internal class (my code is often common), the compiler often does not display the following error information: empty Final is initialized in each constructor, or this error message will occur. 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. Under this specified, the following code is illegal: 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. When accessing non-constant STATIC domains from an instance method, the compiler should request any one of the two structures: or, the compiler should obtain the read / write lock: Another method is (this is also one Ideal method) - The compiler should automatically use a read / write lock to synchronize access non-constant Static domain, so that programmers don'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.

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

New Post(0)