Preface
If the developer is developing or maintaining a servlet-based web application, it is recommended that you check the servlet specification. The content is helpful for Web application developers to understand the working mechanism of servlet containers.
The specification shows how the servlet container processes customer requests. The servlet container creates a singleton according to each Servet defined in the web. xml configuration file. Therefore, multiple client requests may simultaneously access these Singleton instances, that is, multiple threads can simultaneously access them. It is important to ensure thread security in Web applications. Developers should be cautious about this issue and ensure that their code must run in a thread-safe manner.
Audit thread security
Most Java developers should have heard of the synchronized keyword. Without using any third-party libraries, Java itself provides native support for threads, and the synchronized keyword is often the most important factor in implementing thread security in Java applications. Synchronization in Java provides mutex support. By synchronizing a piece of code or the whole method, we can ensure that only one thread can execute it at most, thus implementing thread security. Introduced synchronization has side effects, that is, blocking. For example, the front-end lady of a large company or lawyer's office needs to process phone numbers, emails, and customers interviewed at the same time. This made her work very busy and caused some tasks to be unable to be handled in time.
Be cautious about blocking in Web applications. A code block protected by synchronization reduces the throughput for processing customer requests at the same time, and many customers are blocked unless a customer completes the processing. Mutual exclusion not only blocks but also deadlocks. Generally, deadlocks cannot be recovered. The following conditions will trigger a deadlock: thread a locks the resources waiting for thread B, and thread B locks the resources waiting for thread A, that is, thread B is waiting for thread a to release the lock, this is also true for thread. Therefore, for multi-threaded applications, deadlock prevention and processing are usually a headache.
In addition, the synchronized keyword also enables a large number of synchronization objects to be used everywhere, which introduces the possibility of deadlock. For example, the methods provided in Java. util. hashtable and Java. util. Vector are mutually exclusive, so do not use them unless you do need them. Developers only need to use Java. util. hashmap and Java. util. arraylist. Of course, the synchronization method in Java. util. Collections also uses the synchronized keyword.
Although reentrant is easier to manage, it introduces other issues. Reusable code prevents data sharing between threads. Consider the following code (let's consider the Java method as thread-safe ):
Public double Pi (){ Int A = 22; Int B = 7; Return New Double (A/B ); } |
No matter how many threads enter the method at the same time, it is always thread-safe. Each thread maintains the stack of each thread and shares the stack with other threads. The method variables created by each thread in the current method (including static methods) only belong to the current thread, that is, stored in the stack of the current thread. Therefore, when both threads a and B enter the preceding method, both A and B will be created. Since the preceding method does not share data, the preceding method is thread-safe. Note: 22/7 values are close to Pi values, but they are not equal.
Next, let's take a look at how to optimize the above Code.
Private double Pi = NULL; Public double Pi (){ If (Pi = NULL ){ Pi = new double (22/7 ); } Return PI; } |
Although the improved method can improve the performance, it is not thread-safe. For example, if Pi is null and thread a and thread B both enter 4th rows. Therefore, thread a and thread B both test whether pi is null and both return true. Next, if thread a continues to execute (thread B is temporarily suspended for some reason), then a reference to the memory address is returned. The memory address contains 22/7 results, that is, the PI value. Finally, thread a exits. When thread B enters the 5th line again, the new memory address will overwrite the original memory address (provided by thread ). This is too dangerous and it is often difficult to debug.
If threadlocal is used, the PI () method can guarantee thread security and improve performance. Private Static threadlocal Pi = new threadlocal ();
Public double Pi (){ If (PI. Get () = NULL ){ Pi. Set (New Double (22/7 )); } Return (double) PI. Get (); } |
The threadlocal class can wrap any object and bind the object to the current thread so that it is only used by the current thread. When the thread executes the PI () method for the first time, the get () method returns NULL because no object is bound to the pi of the threadlocal instance. The Set () method can be used to bind an object to the current thread and is not used by other threads. Therefore, if different threads need to frequently access the PI () method, threadlocal can not only ensure thread security, but also improve performance.
Currently, there are many resources about how to use threadlocal. Before Java 1.4, threadlocal had poor performance, but this problem has been solved. In addition, many developers misuse threadlocal due to its incorrect understanding. Note that the threadlocal method is absolutely correct for the above instance. After threadlocal is introduced, the behavior of the above method has not changed, but the method is thread safe.
Code for thread-safe development through reentrant requires developers to use instance variables or static variables with caution, especially for modifying the objects required by other threads. In some cases, it may be more appropriate to use synchronization. However, to identify application performance bottlenecks caused by synchronization, you can only perform performance testing using professional performance evaluation tools or load testing.
Thread security in Web Applications
After reviewing thread security knowledge, let's study how thread security works in Web applications! Developers can operate databases by creating web pages. For example, you can operate RDBMS on both the web layer and the business logic layer. This article uses hibernate to persistently store the business model to the database. On the web layer, developers can use tapestry, Wicket, struts, webwork, JSF, spring MVC, or other Web frameworks running in Web containers.
The specific implementation of the web layer is not the focus of this article. This article will focus on how to manage database connections, which is a frequently-considered resource for processing thread security issues in Web applications. Database Connection objects, such as connections, result sets, statement, and hibernate sessions, are stateful objects. Of course, they are not thread-safe, so they cannot be accessed by multiple threads at the same time. As mentioned above, developers should avoid using synchronization whenever possible. Whether it is the synchronized keyword or those synchronization classes (hashtable or vector), avoid using it whenever possible. Therefore, if you use reentrant, you do not need to handle blocking or deadlocks.
Of course, implementing thread security through reentrant to access the database is not a simple task. For example, some developers may add filters in the servlet container configuration. Therefore, when the customer requests, the filter will create a JDBC connection or hibernate session and bind them to the current thread with the help of the threadlocal class for business logic use. To directly use J2EE APIs, developers need to manage transactions, DB errors, and other development content in addition to many operations unrelated to the business logic. Note that maintenance of these operations that are not related to the business logic is often time-consuming.
Spring intrusion
Some Java developers may have heard about the DaO abstraction provided by spring. Of course, some developers may have used it. With the template provided by spring, developers can reuse Dao code. With Spring AOP, developers can also use declarative transactions. Therefore, this article studies how spring implements thread-safe access to RDBMS. For example, spring allows JDBC, hibernate, JDO, ibatis, toplink, and other methods to access the database. The following examples are common scenarios in enterprise applications.
First, define the data source and use it for hibernate sessionfactory.
<Bean Id = "propertyconfigurer" class = "org. springframework. Beans. Factory. config. propertyplaceholderconfigurer"> <Property name = "locations"> <List> <Value> WEB-INF/jdbc. properties </value> </List> </Property> </Bean><Bean id = "datasource" class = "org. Apache. commons. DBCP. basicdatasource" Destroy-method = "close"> <Property name = "driverclassname"> <value >$ {JDBC. driverclassname} </value> </property> <Property name = "url"> <value >$ {JDBC. url} </value> </property> <Property name = "username"> <value >$ {JDBC. Username} </value> </property> <Property name = "password"> <value >$ {JDBC. Password} </value> </property> </Bean> <Bean id = "sessionfactory" class = "org. springframework. Orm. hibernate. localsessionfactorybean"> <Property name = "datasource"> <Ref bean = "datasource"/> </Property> <Property name = "mappingdirectorylocations"> <List> <Value> classpath: </value> </List> </Property> <Property name = "hibernateproperties"> <Props> <Prop key = "hibernate. dialect"> net. SF. hibernate. dialect. hsqldialect </prop> <Prop key = "hibernate. show_ SQL"> true </prop> </Props> </Property> </Bean> |
This is a typical configuration of hibernate, that is, connecting to the database through the defined data source, and creating the hibernate sessionfactory through the local sessionfactory. Next, you need to define the Business Object (access to the database) and the Transaction Manager (manage local transactions through the hibernate session ). The method exposed by the business object can add new records to the database, while the Transaction Manager can wrap the method in the transaction. They are defined as follows.
Public interface customerdao { Public void createcustomer (customer ); }Public class hibernatecustomerdao implements customerdao { Private hibernatetemplate = NULL; Public void setsessionfactory (sessionfactory ){ This. hibernatetemplate = new hibernatetemplate (sessionfactory, false ); } Public void createcustomer (customer ){ This. hibernatetemplate. Save (customer ); } } |
As you can see, the above class uses the hibernatetemplate provided by spring. Note that the development of the template follows the best practices in the industry and does not relate to the business. However, the code to be processed by the J2EE API is removed. At the same time, it converts the checked exception to a non-checked exception through the DaO abstraction. Of course, spring not only provides templates for using hibernate, but also provides similar templates for JDBC, ibatis, sqlmap, JDO, and toplink. Since these template classes and their instance variables implement reentrant, they are thread-safe, so concurrent threads are allowed to use the template at the same time. Using these templates not only enables code reuse, but also provides best practices. In addition to thread-safe access to DB, templates also provide a lot of meaningful content. Okay. Let's take a look at how to define business objects and transaction managers!
<Bean id = "customerdaotarget" class = "test. usecase. hibernatecustomerdao"> <Property name = "sessionfactory"> <ref bean = "sessionfactory"/> </property> </Bean><Bean id = "transactionmanager" class = "org. springframework. Orm. hibernate. hibernatetransactionmanager"> <Property name = "sessionfactory"> <ref bean = "sessionfactory"/> </property> </Bean> <Bean id = "customerdao" class = "org. springframework. transaction. Interceptor. transactionproxyfactorybean"> <Property name = "transactionmanager"> <ref bean = "transactionmanager"/> </property> <Property name = "target"> <ref bean = "customerdaotarget"/> </property> <Property name = "transactionattributes"> <Props> <Prop key = "create *"> propagation_required </prop> <Prop key = "*"> propagation_required </prop> </Props> </Property> </Bean> |
If the developer is not familiar with the transaction management configuration in spring, this article is just for you. First, the above spring configuration snippet defines the Business Object hibernatecustomerdao, which encapsulates hibernate sessionfactory. Note: by default, the JavaBean defined in spring is single-instance, and hibernatecustomerdao is no exception. This means that multiple threads may execute the createcustomer () method at the same time.
Second, the hibernate Transaction Manager is configured, which encapsulates the same hibernate sessionfactory instance. The transaction manager completes the following tasks each time it executes. First, check whether the hibernate session is bound to the current thread. If it is already bound, use it directly. If not, the Transaction Manager informs hibernate sessionfactory to create a new session and then binds the created session to the current thread. Second, if there is no active transaction, the transaction manager starts a new transaction and wraps the session in. Otherwise, directly participate in the event transaction.
The whole process is implemented by using the transactionproxyfactorybean provided by spring. Of course, this is a declarative transaction management process. Transactionproxyfactorybean can create proxy objects for business objects to manage transactions through the Transaction Manager. Each time the createcustomer () method is called through a proxy object, the Transaction Manager manages transactions based on the transaction attributes. Currently, spring not only provides the hibernatetransactionmanager Transaction Manager, but also provides the corresponding transaction manager for JDBC data sources, JDO, and toplink.
Let's take a look at the Business Objects! When the createcustomer () method is called, hibernatetemplate searches for the hibernate session bound to the current thread. The second parameter passed in to the hibernatetemplate builder is false. Therefore, if the hibernate session is not bound, an unchecked exception is thrown. This is especially useful for scenarios where the transaction management function is incorrectly configured (note that the transaction manager is important ). Once the transaction management is configured, The Hibernate session is bound to the current thread to start the transaction. Please note that hibernatetemplate does not check whether the transaction is activated or start or stop the transaction explicitly. Note that if an unchecked exception is thrown in the declared method (given in the transaction attribute), the current active transaction will be rolled back.
Conclusion
Finally, let's summarize how spring implements data access in thread-safe mode. By using transaction management and balancing the functions provided by threadlocal, spring binds the database connection (JDBC connection, Hibernate session, and JDO persistence manager) to the current thread for the DaO template. At the beginning of this article, we studied that database connections are not shared among threads. Spring not only provides declarative transaction management, J2EE API abstraction, and best practices, but also provides a thread-safe template. When using spring to access the database, it is the most reliable and common practice to implement thread security of applications through reentrant.