Multithreading (6) - to improve the performance of some of the views locks

  Lock is the most common method of synchronization in a highly concurrent environment, fierce competition led to lock the program's performance decline, so we need to discuss a number of issues related to the performance of the lock, as well as some precautions, such as avoiding deadlock. To reduce the lock of competition leads to performance degradation program, you can use the following suggestions to improve it performance.

  1. Reduce lock hold time

  For applications that use concurrency control locks, lock contention in the process, hold time and system performance of a single thread lock has a direct relationship, if the thread that holds the lock of the longer, more intense then the lock of the degree of competition . In the following code fragment as an example:

public synchronized void syncMethod(){
     othercode1();
     mutexMethod();
     othercode2();
}

  In syncMethod () method, it is assumed that only mutexMethod () methods are needed for synchronization, and othercode1 () method and othercode2 () method does not need to make synchronization control. If these values ​​are the heavyweight method of method, it would take a long time in the CPU, high concurrency, synchronization entire approach leads to a substantial increase in waiting thread. Because the thread enters the method of obtaining internal locks, the locks are released only after the implementation of all tasks, optimization is the only synchronization is necessary so that we can significantly reduce the thread holding a lock of time and improve system throughput .

public void syncMethod2(){
     othercode1();
     synchronized(this){
          mutexMethod();
     }
     othercode2();
}

  2. Reduce lock granularity

  It refers to a narrow locking subject, thereby reducing the likelihood of lock conflicts, thereby increasing the capacity of the system complicated. In the JDK typical usage scenario is ConcurrentHashMap, this segment has become several small internal Map of HashMap, called the segment (SEGMENT), default is 16 segments.

  If you need to increase ConcurrentHashMap a new entry, not the entire HashMap lock, but first get the hashcode entry according to which should be stored in the segment, and then lock the segment, and complete the put () method operating. In a multithreaded, if multiple threads simultaneously put () operation, as long as the added entry is not stored in the same segment, the threads can be truly parallel. The default is 16 segments, with luck, be acceptable in 16 threads inserted simultaneously, greatly enhance throughput. The following code process which put () operation method. 5 to 6 lines of code obtained according to the key number j corresponding to the segment, and then to obtain segment s, and then insert the data in a given segment.

public V put(K key, V value) {
     Segment<K,V> s;
     if (value == null)
         throw new NullPointerException();
     int hash = hash(key);
     int j = (hash >>> segmentShift) & segmentMask;
     if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
          (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
         s = ensureSegment(j);
     return s.put(key, hash, value, false);
}

  However, reducing the particle size will bring a new problem when the system requires global lock, which consumes resources on the lot. For example, to obtain global information ConcurrentHashMap, we need to acquire locks on all segments before being successfully implemented. Such that the map size () method returns ConcurrentHashMap and all valid entries, the need to obtain the information acquired locks on all the sub-segments, so the code size () method is as follows:

public  int size () {
     // try several times to obtain an accurate count. If for continuous asynchronous change table fails, use the lock.
    Final Segment <K, V> [] = Segments the this .segments;
     int size;
     Boolean overflow; 
     Long SUM;          // Number of 
    Long Last = 0L;    // a Number of 
    int retries = -1; 
     the try {
         for (;;) {
             IF (retries ++ == RETRIES_BEFORE_LOCK) {
                 for ( int J = 0; J <segments.length; ++ J)
                    ensureSegment (J) .lock (); // for all segments lock 
            } 
            SUM = 0L ; 
            size = 0 ; 
            overflow = to false ;
             for ( int J = 0; J <segments.length; ++ J) { 
                Segment <K , V> SEG = segmentAt (Segments, J);
                 IF (! SEG = null ) { 
                    SUM + = seg.modCount; // Number of statistical
                     int C = seg.count;
                     IF (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();   // 释放所有锁
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

  You can see from the above code size () first attempts to lock-sum, if that fails will try Method locked. Only similar size () method to get the global information infrequent use of the method call when this method reduces the size of the lock in order to improve the throughput of the system in the true sense.

  3. Alternatively separate read and write exclusive lock with lock

  ReadWriteLock using separate read and write locks may improve the performance of the system, this is actually a special case of reducing the particle size, separate read and write lock system functions are divided points. Since the read operation itself will not affect the integrity and consistency of the data, and therefore, in theory, be allowed to read between multiple threads at the same time, read-write lock is the realization of this function. So reading and writing fewer occasions concurrent read-write lock can effectively improve the capability of the system.

  4. Lock isolated

  The write lock is locked the idea extends further separated. The write lock on the read and write operations of different functions, effective separation of the lock. We can be based on features of the application, using similar separation idea, it can also be separated for the exclusive lock. LinkedBlockingQueue implementation, Take () and put () respectively, to achieve the function of taking data from the queue and the data increased, although the function queue has two modifications, but the list is based LinkedBlockingQueue, both the operation and effect of a list head, acting on the tail of a linked list, in theory, the two do not conflict. Use an exclusive lock, then maybe method can not achieve true concurrency, they wait for each other to release a lock resource. JDK separated respectively take () and put () with two locks.

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();

  TakeLock defined above code snippet and putLock, which are used in the take () and put () method, so maybe this method independent of each other, there is no competition between the lock maybe method, only take (), and take ( Room) method, put () and put () respectively between takeLock method and putLock compete, thereby weakening the lock contention. take () method implementation is as follows:

public E Take () throws InterruptedException { 
    E X; 
    int C = -1 ;
     Final of AtomicInteger COUNT = the this .count;
     Final of ReentrantLock = takeLock the this .takeLock; 
    takeLock.lockInterruptibly (); // can not have two threads simultaneously fetch data 
    try {
         the while (count.get () == 0 ) {// If no data currently available, waits 
            notEmpty.await (); // wait put () method of operating a notification 
        } 
        X = dequeue (); // get the first a data 
        C = count.getAndDecrement (); // Save a number, atomic operation, and thus put () simultaneously access count
        IF (C>. 1 ) 
            notEmpty.signal (); // notify other take () method of operating a 
    } the finally { 
        takeLock.unlock (); // release lock 
    } 
    IF (C == Capacity) 
        signalNotFull (); // put notifications () operation, a free space existing 
    return X; 
}

  put () method to achieve the following:

public  void PUT (E E) throws InterruptedException {
     IF (E == null ) the throw  new new a NullPointerException (); 
    int C = -1 ; 
    the Node <E> = Node new new the Node (E);
     Final of ReentrantLock = putLock the this .putLock;
     Final = COUNT of AtomicInteger the this .count; 
    putLock.lockInterruptibly (); // can not have two threads simultaneously put () method of 
    the try {
         the while (count.get () == Capacity) {// queue is full
            notFull.await (); // wait 
        } 
        the enqueue (Node); // data insertion 
        c = count.getAndIncrement (); // Update the total number, c is the count by the variable value before. 1
         IF (c +. 1 < Capacity) 
            notFull .signal (); // there is sufficient space to notify other threads 
    } the finally { 
        putLock.unlock (); // release lock 
    } 
    IF (C == 0 ) 
        signalNotEmpty (); // after successful insertion notification take () method data fetch 
}

  The lock coarsening

  Typically, in order to ensure effective concurrency among multiple threads, each thread will be asked to hold a lock of time as short as possible, immediately release the lock after use of resources. The only way to get the resources to perform tasks waiting in the other thread locked as soon as possible, but if the same non-stop lock request, synchronized release, which also consume system resources, but not conducive to performance optimization. To this end, the virtual machine in the face of a series of continuous operation with a lock on the ongoing request and release, it will put a request to integrate all the locks operate in pairs locks, thereby reducing the number of synchronization requests for the lock, this operation is called lock coarsening.

  During development, we can lock coarsening in reasonable cases, especially when the inner loop lock request, because each loop has a lock and release the lock application operation, in fact, it is entirely unnecessary, just outside locked loop it.

Guess you like

Origin www.cnblogs.com/wangyongwen/p/11257517.html