Java high concurrency from zero to give up

foreword

This article mainly explains how to optimize the locking mechanism
or overcome the problem that multi-threading can cause performance degradation due to locks

ThreadLocal thread variable

There is such a scene, there is a large bucket of water in front, and 10 people go to drink water. In order to ensure thread safety, we need to lock the cups so that
everyone queues up to drink water, because the locked cups are synchronized, and only one
It takes a long time for everyone to drink water from the only
cup. What if we distribute a cup to everyone? Is the time for each person to drink water reduced to 1/10?

Multi-threaded concurrency is also a reason.
Each Thread has its own data storage space (ThreadLocalMap)
, and ThreadLocal stores data in the storage space of the current thread. In the
following example, an arraylist is stored in each thread, not everyone. share an arraylist

public class ThreadLocalTest {
    public static ThreadLocal threadLocal = new ThreadLocal();
    public static ArrayList list = new ArrayList();
    public static class Demo implements Runnable {
        private int i;
        public Demo(int i) {
            this.i = i;
        }
        @Override
        public void run() {
            list.add(i);
            threadLocal.set(list);
            System.out.println(threadLocal.get());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int j = 0; j < 200; j++) {
            es.execute(new Demo(j));
        }
        Thread.sleep(3000);
        System.out.println(list.size());
        es.shutdown();
    }
}

 Inside each thread there is a storage area called ThreadLocalMap

It can be seen that ThreadLocal uses set and get to access values.
Only when the thread is completely closed, the data in ThreadLocalMap will be recycled by GC

At this time, there is a problem worth considering.
When we use the thread pool for development, the thread in the thread pool will not be closed, it will only be in an idle state.
That is to say, if we store too large data in the ThreadLocalMap of the current thread , the thread is constantly called and is idle...
Finally, it will lead to memory overflow . The solution is to use the ThreadLocal.remove() method to remove the variable
when the data is not needed.

CAS operation

There is also a mechanism for getting out of the lock, that is, CAS
CAS carries three variables, namely:
V update variable: variable to be returned
E expected value: original value
N new value, new variable passed in

Only when the expected value and the new value are equal, will V=N be set. If they are not equal, it means that the operation will make the data unable to synchronize.
According to the above explanation, it is probably known that CAS is actually protecting the synchronization of the data.

When multiple threads perform CAS operations, it can be imagined that only one thread can successfully update, and then the E and V of other threads will be constantly compared,
so the implementation of the CAS synchronization lock is the same

The concurrent package of CAS operation is in the Atomic package, atomic implements many types,
whether it is AtomicInteger or AtomicReference, all have the same points, please observe their source code:

private volatile V value;
private static final long valueOffset;

 The above is AtomicReferenc

private volatile int value;
private static final long valueOffset;

 The above is AtomicIntege

both have value, which is their current actual value


valueOffset holds the offset of the value

A simple AtomicIntege example is given below:

public class AtomicTest {
    public static AtomicInteger atomicInteger = new AtomicInteger();
    //public static AtomicReference atomicReference = new AtomicReference();
    public static class Demo implements Runnable{
        @Override
        public void run() {
            for (int j=0;j<1000;j++){
                atomicInteger.incrementAndGet(); //Add 1 to the current value and return the current value
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i =0;i<10;i++){
            es.submit(new Demo());
        }
        Thread.sleep(5000);
        System.out.println(atomicInteger);
    }
}

 You try to execute it, if it prints 10000, it means thread safety


Using CAS operations has better performance than synchronized locks

Let's look at incrementAndGet()the source code:

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

 Take a look at the getAndAddInt()source code:

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
    }

 There is a loop here. If you look closely at the source code, you will find that it is native. Although you cannot see the native code, you can see that it has performed a CAS operation here, and constantly compares multiple variables. Only the preset value and the new value are equal. when the loop is exited

var5 is the variable that needs to be updated, var1 and var2 are the default and new values

deadlock

After talking about so many lock-free operations, let's take a look at a deadlock phenomenon.
Two threads occupy each other's locks, and a deadlock situation will occur.

public class DeadLock extends Thread{
    protected String suo;
    public static String zuo = new String();
    public static String you = new String();
    public DeadLock(String suo){
        this.suo=suo;
    }
    @Override
    public void run(){
        if (suo==zuo){
            synchronized (zuo){
                System.out.println("Get the left, is getting the right...");
                synchronized (you){
                    System.out.println("Get the right and succeed");
                }
            }
        }
        if (suo==you){
            synchronized (you){
                System.out.println("Get the right, is getting the left...");
                synchronized (zuo){
                    System.out.println("Get zuo, success");
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10000;i++){
            DeadLock t1 = new DeadLock(zuo);
            DeadLock t2 = new DeadLock(you);
            t1.start();t2.start();
        }
        Thread.sleep(50000);
    }
}

 

As shown in the figure:

 

images/jEyDXhKChpCFfx46fhJwbmdDRcm68sj3.png

 

There is a deadlock phenomenon between two threads, so unlocking can not only improve performance, but also prevent deadlock.

 

The address of this article is https://segmentfault.com/a/1190000012218687

 

More participation content: http://www.roncoo.com/article/index?tn=JAVA

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326004726&siteId=291194637