Admit that some of the title party tastes, but it is used in the actual asynchronous framework.
3-4 faster performance and lower CPU occupancy compared to the "accepted" concurrenthashmap approach
Demand
The asynchronous framework requires a buffer that holds the request data and is shared over multiple threads.
This is obviously a multithreaded concurrency problem.
Synchronous Lock Scheme
began to underestimate the problem, thought that simply lock resources, insert Request object, are memory operations, time is short, even if "blocking" is not serious.
Private voidMultithreadsynclock (Final intNumofthread,Finalmap<string,string> map)throwsException {Final Long[] errcount=New Long[Numofthread+1]; Thread T=NewThread (NewRunnable () { Public voidrun () { for(inti = 0; i < Numofthread; i++) { NewThread (NewRunnable () { Public voidrun () {String Val=Uuid.randomuuid (). toString (); String Key=Thread.CurrentThread (). GetName (); intIndex=integer.parseint (key.substring (7, Key.length ())) +1; Longt1=System.currenttimemillis (); for(intj=0;j<10000;j++) { synchronized(map) {map.put (key,val);}//Insert after acquiring lock if(!(val). Equals (Map.get (key))) errcount[0]++;//ErrCount >1 means reading data and writing different } LongT2=System.currenttimemillis (); Errcount[index]=+errcount[index]+t2-T1; } }, "Thread-" +i). Start (); } } }, "Yhread-main"); T.start (); Thread.CurrentThread (). Sleep (1000); T.join (); LongTt=0; for(inti=1;i<=numofthread;i++) tt=tt+Errcount[i]; Log.debug ("numofthread={},10,000 per thread,total time spent={}", NUMOFTHREAD,TT); Assert.assertequals (0,errcount[0]); } Synchronous lock Test Code
The results are appalling! And as the number of concurrent threads increases, "plugging" is serious
concurrent, each thread requests a plug-in Data 10000 times, total time consuming |
200 concurrent, per-thread request plug-in Data 10000 times, total time consuming |
| 4567.3ms |
20423.95ms |
Spin lock
@Test Public voidMULTITHREADPUTCONCURRENTHASHMAP100 ()throwsexception{FinalMap<string,string> map1=NewConcurrenthashmap<string,string> (512); for(inti=0;i<100;i++) Multithreadputmap (100, MAP1); } Private voidMultithreadputmap (Final intNumofthread,Finalmap<string,string> map)throwsException {Final Long[] errcount=New Long[Numofthread+1]; Thread T=NewThread (NewRunnable () { Public voidrun () { for(inti = 0; i < Numofthread; i++) { NewThread (NewRunnable () { Public voidrun () {String Val=Uuid.randomuuid (). toString (); String Key=Thread.CurrentThread (). GetName (); intIndex=integer.parseint (key.substring (7, Key.length ())) +1; Longt1=System.currenttimemillis (); for(intj=0;j<10000;j++) {map.put (key,val);//the implementation of map Concurrenthashmap and HashMap if(!(val). Equals (Map.get (key))) errcount[0]++;//ErrCount >1 means reading data and writing different } LongT2=System.currenttimemillis (); Errcount[index]=+errcount[index]+t2-T1; } }, "Thread-" +i). Start (); } } }, "Yhread-main"); T.start (); Thread.CurrentThread (). Sleep (1000); T.join (); LongTt=0; for(inti=1;i<=numofthread;i++) tt=tt+Errcount[i]; Log.debug ("numofthread={},10,000 per thread,total time spent={}", NUMOFTHREAD,TT); Assert.assertequals (0,errcount[0]); } Spin lock test code
Using Concurrenthashmap concurrent , each thread requests an insert Data 10000 times, time consuming |
Using Concurrenthashmap Each thread requests an insert. Data 10000 times, time consuming |
Using Concurrenthashmap concurrent , each thread requests an insert Data 10000 times, time consuming |
| 200.69ms |
402.36ms |
542.08ms |
Compared with the synchronous lock, the efficiency is improved a lot, about 22-50 times, an order of magnitude gap!
Spin lock, the thread has been running, to avoid congestion, wake-to-back switching overhead, and a critical state of an instruction to complete, greatly improving efficiency.
Can it be further optimized?
As we all know, HASHMAP data structure is an array + linked list (refer to the online HashMap source analysis).
In simple terms, each time you insert data:
- Converts the given key to the array pointer p
- If ARRAY[P] is empty, save the Vlaue to Array[p]=value and complete the insert
- If ARRAY[P] is not empty, create a linked list, insert two objects, and save the linked list to array[p].
- When the fill rate is to the default of 0.75, it causes expansion.
Javadoc explicitly states that HashMap is not thread safe.but to be precise, the insecurity lies in:1, key repeat, linked list inserted. 2, expansion, filling rate of less than 0.75 in the guarantee key is not repeated, it should be the hash of key is not repeated, while the filling rate is less than 0.75 cases, multi-threaded insert/Read security . Therefore, can be further optimized, using the thread name as key, the request data, inserted into the hashmap. Avoid the lock!
Of course someone would ask
-
- is the thread name unique?
- How to guarantee the different thread name hash, corresponding to different pointers?
- Does the line routines insert multiple data?
- HashMap what happens to the expansion?
- Is performance improved?
Before answering, look at how Tomcat handles the request: When the request is reached, Tomcat pulls the idle thread from the thread pool, executes the filter, and the last servlet.
The Tomcat processing request has the following characteristics:
-
- Process thread, cannot process new request until result is returned
- The processing thread pool is usually small, about 200-300. (now more inclined to use multiple "small" size tomcat instances)
- Thread name Unique
So
Question 1, 3 is obviously OK.
Issue 4, when initializing Hasmap, allocate a large space beforehand to avoid the expansion. For example, for 300 concurrency, New HASHMAP (512).
Although there are many requests, there are only 300 threads.
Problem 2,java is optimized for hash, which ensures the uniformity of hash and avoids repetition.
Public voidCheckhashcodespreadoutenough () {intlength=512; for(intj=0;j<10000;j++) {//Repeat 1000 times,Map<string,object> map=NewHashmap<string,object>(length); for(inti=0;i<300;i++) {String key= "thread-" + (i+1); intHashcode=hash (key,length); Integer Keyhashcode=NewInteger (hashcode); Log.debug ("Key={} hashcode={}", Key,hashcode); if(Map.containskey (Keyhashcode)) {//The generated hash value is saved as a key in the hash table, as long as the duplicate indicates a conflictLog.error ("Encounter collisions! key={} hashcode={} ", Key,hashcode); Assert.asserttrue ("Encounter Collisions!",false); } } } } /** Calculation method of hash value extracted from HashMap source * Tracking code is generally not used Sun.misc.Hashing.stringHash32 ((String) k) calculation * About StringHash32, net Have comments and are interested to check. */ Private intHash (Object K,intlength) { intH = 0; H^=K.hashcode (); H^= (H >>>) ^ (H >>> 12); H=h ^ (h >>> 7) ^ (H >>> 4); returnH & (length-1); } The code above, hash the 300 thread names, detect conflicts, and run 10,000 times repeatedly. The result shows that there is no conflict. In other words HashMap hash algorithm uniformity is not a problem, especially in this case environment, can guarantee the hash unique! Problem 5, performance issues, see Unit test results
Publiwuwu void throws exception{ final map<string,string> map=new// replacement Map implemented as HashMap for (int i=0;i<100;i++) multithreadputmap (map); } // Multithreadputmap (100,map); see Concurrenthashmap Unit Test Code
hashmap no lock concurrency
use hashmap 100 concurrency per thread request Insert data 10000 times, time consuming |
use hashmap 200 concurrency , per thread request insert data 10000 times, time consuming |
use hashmap 300 concurrency Span class= "Font3" >, per thread request Insert data 10000 times, time consuming |
| 46.79ms |
99.42ms |
137.03 |
| increase 4.289164351 times times |
increase 4.04707 3,024 times times |
increase by 3.955922061 times times |
There are nearly 3-4 times the increase
Conclusion
- Under certain circumstances, HashMap can be used in multi-threaded concurrency environment
- A sync lock, which belongs to a sleep-waiting type of lock. The state changes, causing the CPU to switch back and forth. thus inefficient.
- Spin lock, always occupy the CPU, keep trying until success. Sometimes the single-core situation causes "suspended animation"
- Careful, analysis, can find no "lock" way to solve multi-threaded concurrency problems, to achieve higher performance and smaller costs
Other
In i5-2.5g,8g win10 jdk1.7.0_17,64bit
Test data, not considering garbage collection, the data some fluctuations.
HashMap The default fill factor is 0.75, which is modified in the construction method.
Using HashMap to implement concurrency