Original technical article, copyright belongs to the author, if reproduced, please indicate the source of public number, to be determined, the original public number has been frozen for a long time

preface

The last article introduced the principle of multi-threading and its significance. At the end of the article, we raised the issue of thread safety, which is also a word we often hear in high concurrency. This article mainly introduces how to quickly use the lock mechanism provided by JDK in the single instance service, and understand its principle. The rest of AQS and Condition are left to explain later. The full text is pure hand typing, the code is pure hand writing, the picture is pure hand drawing, I hope to bring you some harvest, if there is any wrong place, welcome to correct.

Think, what scenarios are thread unsafe

We often emphasize thread safety when developing high-concurrency applications. What kind of scenarios are unsafe?

Shangdao (An) Gu (Li), the example of moving goods was mentioned in the last chapter. I wonder if you have tried the last case.

In the case mentioned in the last article,

At present, there is only one person to move (A) a pile of goods in the warehouse, tentatively 1,000,000 pieces. If A can only move one piece at a time, a total of 1,000,000 times will be needed. If one person can be added to move with A, assuming that the two people take the same time to move, then they can move 500,000 times each.

As a result of the previous section,

Transfer completed, Employee: B, transfer: [272708] times Transfer completed, Employee: A, transfer: [727292] times

Reflections on the previous chapter

Synchronized and AtomicInteger are used in the example to ensure that the total amount of goods remaining is handled by multiple people. The value is also correct. AtomicInteger can be changed into Integer or synchronized can be removed. The total amount of the two people’s final handling will be inconsistent with the total amount of the original goods.

Let’s remove synchronized and replace AtomicInteger with int

# move goodsStatic class Carry extends Thread{/** Carry */ private String peopleName; /** private int carryNum; public Carry(String peopleName) { this.peopleName = peopleName; } @Override public voidrun() {
        while(! isInterrupted()) {if (cargoNum > 0) {
                    cargoNum--;
                    carryNum++;
                } else {
                    System.out.println("Handling completed, employee:" + peopleName + ", carry: [" + carryNum + "]"); interrupt(); }}}}Copy the code

Open move

Static int cargoNum = 1000000; static int cargoNum = 1000000; public static void main(String[] args) { Carry carry1 = new Carry("甲");
    Carry carry2 = new Carry("乙");

    carry1.start();
    carry2.start();
}
Copy the code

The results of

Transfer completed, Employee: B, transfer: [983347] transfer completed, Employee: A, transfer: [995228]Copy the code

We found that there were only 1,000,000 goods in total. To be reasonable, they should have moved 1,000,000 times in total in the end, but they both moved nearly 1,000,000 times. Isn’t it repetitive labor? Yes, this is caused by thread insecurity in multithreading.

Let’s think about why,

A, b two people carrying, at the same time when a move to get the total remaining quantity of goods, and then 1, own workload + 1, and then move out the goods, the same is true of b, seems as if no problem, but imagine if a, b move at the same time, to get the remaining amount at the same time, such as 1000, and at the same time – 1, so two people think the rest of the 999, This creates a surplus of labor.

If we can guarantee that when a updates the remaining total, B will not update, and vice versa, when B updates, A will not update, then ultimately this problem will not occur.

So you’re going to use a lock, add a lock, lock it while I’m using it, you wait, I’m done, open the lock, you use it, so that you can keep your data safe.

Java Concurrency In Practice defines thread safety as the following: When multiple threads are accessing an object, there is no need for additional synchronization or any other coordination on the caller’s side without taking into account the scheduling and alternate execution of those threads In the runtime environment. The object is thread-safe if the correct result is obtained by calling it.

Built-in lock

synchronized

Synchronized is a built-in mutex provided by JDK, which is roughly divided into class lock and object lock. As the name implies, class lock is the object (class) of the locking class file, and object lock is the instance object of the locking class.

The following is a brief understanding of how this works, which will be further dissected in subsequent bytecode and JMM chapters.

Synchronized implementations rely on Monitor, and an object instance is associated with a Monitor, referring to the JVM specification

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref

The relationship information is mainly contained in the object header, which is divided into two parts: Mark Word (marking information) and Klass Pointer (pointing to class metadata). (More on object analysis in a future bytecode chapter)

Where LockWord in Mark Word points to a Monitor, which contains information as shown in the figure above.

  1. OwnerIs null when initialized and identifies the thread when held by a thread
  2. EntryQ, associated with a mutex (Semaphore) that blocks other threads that want to hold Monitor
  3. RCThis, marks the number of threads that are locked or waiting
  4. Nest, and records the reentrant times
  5. HashCodeHashCode copied from the object header
  6. Condidate, the successor thread that needs to be woken up, if none, 0

To summarize, if a thread wants to lock an object, it needs to hold the Monitor associated with the object, and other threads (Thread2, Thread3) block. The JVM is implemented through CAS, which will be explained later in this article.

Kind of lock

Class locks lock class objects, class files that are objects in the JVM

For example, the following

First we create a print class

public class Print {

    public void print() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + " print.....");
        # 1 s sleep
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " print end....."); }}Copy the code

Thread class

static class PrintThread extends Thread {

    @Override
    public void run() {
        Print print = new Print();
        # locks in classsynchronized (Print.class) { try { print.print(); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

perform

public static void main(String[] args) {
    PrintThread printThread1 = new PrintThread();
    PrintThread printThread2 = new PrintThread();

    printThread1.start();
    printThread2.start();
}
Copy the code

The results of

Thread-0 print.....
Thread-0 print end.....
Thread-1 print.....
Thread-1 print end.....
Copy the code

We can see that although both threads are calling each Print instance, the second thread is obviously blocked, and if we remove synchronized (print.class), the result is as follows

Thread-0 print.....
Thread-1 print.....
Thread-0 print end.....
Thread-1 print end.....
Copy the code

The two threads are executing in parallel, and adding synchronized to a method that calls the static modifier also locks the class, which we won’t demonstrate here.

Object lock

Object locks instance objects

Let’s modify the thread class, and print instance is passed in via construct

static class PrintThread extends Thread {

    private Print print;

    PrintThread(Print print) {
        this.print = print;
    }

    @Override
    public void run() {
        synchronized (print) { try { print.print(); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code

Execute class, again two threads passing in different class instances

public static void main(String[] args) {
    PrintThread printThread1 = new PrintThread(new Print());
    PrintThread printThread2 = new PrintThread(new Print());

    printThread1.start();
    printThread2.start();
}
Copy the code

The results of

Thread-0 print.....
Thread-1 print.....
Thread-1 print end.....
Thread-0 print end.....
Copy the code

As you can see, the different threads do not affect each other

So, what if we pass in the same instance?

Perform class

public static void main(String[] args) {
    Print print = new Print();
    
    PrintThread printThread1 = new PrintThread(print);
    PrintThread printThread2 = new PrintThread(print);

    printThread1.start();
    printThread2.start();
}
Copy the code

The results of

Thread-0 print.....
Thread-0 print end.....
Thread-1 print.....
Thread-1 print end.....
Copy the code

As you can see, for the same print instance, the two threads execute sequentially. The first thread holds the lock, and the second thread blocks until the first thread completes.

volatile

Volatile is one of the lighter locks provided by the JDK than synchronized, so to help understand that, let’s start with two appetizers.

Data sharing between threads

Now we have a variable a that is 1, and thread 1 and thread 2 both need to update the value of A, so what is the value of A after the simultaneous operation?

Obviously, a could end up being 2, or it could end up being 3, so if thread 1 gets a, it gets a 1, it gets 2, but it hasn’t been synchronized back into main memory, so thread 2 gets a a and it’s still 1, it’s set to 3, isn’t that unsafe? In addition to thread safety with synchronized as described earlier, we can also use volatile.

If a field is declared volatile, the Java thread memory model ensures that all threads see the variable’s value as consistent. However, only atomic operations are supported to be visible, such as

# two threads do ++ on I at the same time
public class VolatileTest {

    static volatile int i = 0;

    static class AddThread extends Thread {

        @Override
        public void run() {
            for (int j = 0; j < 100000; j++) {
                i++;
            }
        }
    }

    public static void main(String[] args) {
        AddThread add1 = new AddThread();
        AddThread add2 = new AddThread();

        add1.start();
        add2.start();

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println("I ends up being"+ i); }}Copy the code

The results of

I ends up being 177,762Copy the code

Ultimately, I should be 200,000, so volatile does not guarantee thread-safety for nonatomic operations.

Add a few more features of volatile:

  1. Atomicity, which is thread-safe for atomic operations such as assignment, true&false, etc. A++ is not considered atomic operations
  2. Visibility. As mentioned above, variables that are volatile can be seen by other threads immediately after the atomic operation value changes
  3. To prevent reordering, as follows,

The JVM runs the compiler and processor to reorder instructions for performance optimization.

public class VolatileDemo {

    private int i = 0;
    private boolean flag = false;

    public void write() {
        # 1 operation
        i = 1;
        # 2 operation
        flag = true;
        System.out.println("Update completed...");
    }

    public void read() {
        # 3 operation
        if (flag) {
            # 4 operation
            System.out.println("I is updated to I ->"+ i); }}}Copy the code

The normal write operation is 1->2 and the read operation is 3->4. However, if an instruction rearrangement occurs, the write operation may become 2->1. Therefore, the read operation may not be complete and the value is read, so we can use volatile to modify flag

private volatile boolean flag = false;
Copy the code

This prevents the compiler or processor from reordering instructions based on memory barriers

The memory barrier instructions
StoreStore Disallow normal writes above and volatile write reordering below
StoreLoad Prevents volatile writes above from potentially volatile read/write reordering below
LoadLoad Disallow all normal reads below and volatile read reordering above
LoadStore Disallow all normal writes below and volatile read reordering above

For variables that are volatile

operation instructions
Volatile write Insert a StoreStore barrier before.
Volatile write Then insert a StoreLoad barrier.
Volatile read Insert a LoadLoad barrier in front.
Volatile read After inserting a LoadStore barrier.

To sum up, we can draw the following anti-rearrangement rules: NO stands for prohibition of rearrangement

ThreadLocal

ThreadLocal can provide thread-local variables that can be shared within the same thread, and separate threads from each other.

A simple example

Create thread copy, default to 1
static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
        new Thread(() -> {
            # get the value
            int n = threadLocal.get();
            # + +
            n++;
            System.out.println(Thread.currentThread().getId() + ":"+ n); }).start(); }}Copy the code

The results of

13: 2
15: 2
14: 2
12: 2
16: 2
Copy the code

We can see that each thread gets a value from a threadLocal that is independent of each other

Let’s tear the source code by hand

The set () method

/**
 * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! = null) map.set(this, value);else
        createMap(t, value);
}
Copy the code

Look at the comment and set the current thread copy of this thread-local variable to the specified value. It’s a bit of a mouthful, so we can see that we’re getting the current thread and we’re calling getMap(t), so let’s look at this method

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code

Get the current Thread t.t hreadLocals, returns the ThreadLocal. ThreadLocalMap, here we first make clear the first layer, the Thread will contain a ThreadLocal. ThreadLocalMap

Then I see the ThreadLocal. ThreadLocalMap

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" inthe code that follows. */ static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; / /... I'll do a little bit later, but I'll focus on the topCopy the code

A ThreadLocalMap defines an Entry, a key is a WeakReference to a ThreadLocal, and a value is the value we save.

We usually use a strong reference, such as Object o = new Object(), o and Object are strong references, if the reference, GC, o will not be recycled, and weak reference, in the next GC, the Object will be recycled.

Yes, back to the set (), we obtained the relationship between general Thread – > ThreadLocal. ThreadLocalMap – > ThreadLocal, well, seems to be a bit around, it doesn’t matter, later have eggs!

Let’s look at the get() method

/**
 * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local
 */
public T get() {
    Get the current thread
    Thread t = Thread.currentThread();
    Get the ThreadLocalMap of the current thread
    ThreadLocalMap map = getMap(t);
    if(map ! = null) {Get the value of the current thread for the current ThreadLocal
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! = null) { @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}return setInitialValue();
}
Copy the code

If you understand the relationship between Thread, ThreadLocal, and ThreadLocalMap, get() should be easy to understand

And then getEntry(this)

/**
 * Get the entry associated with key.  This method
 * itself handles only the fast path: a direct hit of existing
 * key. It otherwise relays to getEntryAfterMiss.  This is
 * designed to maximize performance for direct hits, in part
 * by making this method readily inlinable.
 *
 * @param  key the thread local object
 * @return the entry associated with key, or null ifno such */ private Entry getEntry(ThreadLocal<? > key) {Get the corresponding value via ThreadLocal, hash algorithm
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    # is the key
    if(e ! = null && e.get() == key)return e;
    else
        return getEntryAfterMiss(key, i, e);
}
Copy the code

If it doesn’t, it’ll loop through getEntryAfterMiss(key, I, e)

/**
 * Version of getEntry method for use when key is not found in
 * its direct hash slot.
 *
 * @param  key the thread local object
 * @param  i the table index for keyHash code * @param e the entry at table[I] * @return the entry associated with key, or null if no such */ # Until found, where markdown displays the bug, o_O private Entry getEntryAfterMiss(ThreadLocal
       key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e ! = null) { ThreadLocal
       k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }Copy the code

ThreadLocal ThreadLocal ThreadLocal ThreadLocal ThreadLocal ThreadLocal ThreadLocal ThreadLocal If you still don’t understand, it doesn’t matter, look at the Easter eggs:

The solid line represents strong references and the dotted line represents weak references. If you understand this diagram, you should be comfortable with ThreadLocal.

However, we often find that using ThreadLocal causes memory leaks, especially when using thread pools. As can be seen from the figure, Entry is a strong reference. If the thread is not finished, this piece of memory will always exist. If the thread pool is used, there will be a large amount of unnecessary data on the same thread.

The explicit lock

lock

Now that we have learned about built-in locks, we will look at a lighter type of lock, explicit locks. This section will mainly explain how to use them (4000 words are written here, so as to avoid excessive use of o_O). The core principle of AQS and Condition will be explained in detail later.

ReentrantLock

Use cases

As usual, let’s start with a non-thread-safe example

# counter
public class Counter {

    private Integer n = 0;

    public void Add() {
        n++;
    }

    public Integer getN() {
        returnn; }}Copy the code

Start two threads, each calling Add () 100000 times

static class AddThead extends Thread {

    private Counter counter;

    AddThead(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            counter.Add();
        }
    }
}

public static void main(String[] args) {
    Counter counter = new Counter();

    for (int i = 0; i < 2; i++) {
        new AddThead(counter).start();
    }

    while(Thread.activeCount() > 2) {
        Thread.yield();
    }

    System.out.println("Result:"+ counter.getN()); } results: 194758Copy the code

The use of the Lock

public class Counter {

    private Lock lock = new ReentrantLock();
    private Integer n = 0;

    public void Add() {
        try {
            lock.lock();
            n++;
        } finally {
            lock.unlock();
        }
    }

    public Integer getN() {
        returnn; }} Result: 200000Copy the code
Writing paradigm
try {
    lock.lock();
    # other
} finally {
    lock.unlock();
}
Copy the code

Release the lock when you’re done

reentrant
public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    # 5 times the lock
    for (int i = 0; i < 5; i++) {
        lock.lock();
    }

    # Unlock 5 times
    for (int i = 0; i < 5; i++) {
        lock.unlock();
    }

    System.out.println("end ....");
}
Copy the code
Acquiring a lock

We start two threads, one holding the lock and the other tryLock()

Attempt to acquire a lock

static Lock lock = new ReentrantLock();

# hold locks
static class Holder extends Thread {
    @Override
    public void run() {
        try {
            lock.lock();
            # sleepThread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); }}}# try to lock
static class TryLock implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        # try to acquire lock, return true
        if (lock.tryLock()) {
            try{
                return true; }finally { lock.unlock(); }}return false;
    }
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
    Holder holder = new Holder();
    holder.start();

    FutureTask<Boolean> futureTask = new FutureTask<>(new TryLock());
    Thread tryThread = new Thread(futureTask);
    tryThread.start();
    Boolean result = futureTask.get();
    System.out.println("result :" + result);
}


result :false
Copy the code

Get lock timeout

Add get wait time, lock.tryLock(6000, timeunit.milliseconds), and then we’ll do it again

The results of

result :true
Copy the code
Fair locks and unfair locks

The so-called fair lock refers to the first come first lock, while the non-fair lock allows preemption in the case of thread switching. Whoever grabs the lock owns the lock.

ReentrantReadWriteLock

Whereas the previously mentioned ReetrantLock runs only one thread at a time, ReentrantReadWriteLock allows multiple threads to execute simultaneously to improve throughput in read and write scenarios.

demo

public class ReentrantReadWriteLockDemo {

    ReadWriteLock lock = new ReentrantReadWriteLock();

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getId() + "Hold write lock");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getId() + "Ready to release write lock");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getId() + "Hold read lock");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getId() + "Ready to release read lock"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.readLock().unlock(); }}}Copy the code

read

public static void main(String[] args) { ReentrantReadWriteLockDemo lockDemo = new ReentrantReadWriteLockDemo(); new Thread(()->{ lockDemo.read(); }).start(); new Thread(()->{ lockDemo.read(); }).start(); } = = = = = = = = = = = = = = = = = = = = = = = the = = = = = = = = = = = = = = = = = = = = = = = = 11 holding read lock 12 read lock to release read lock 12 ready to release lockCopy the code

You can see that two threads can execute simultaneously

Read and write

public static void main(String[] args) { ReentrantReadWriteLockDemo lockDemo = new ReentrantReadWriteLockDemo(); new Thread(()->{ lockDemo.read(); }).start(); new Thread(()->{ lockDemo.write(); }).start(); } = = = = = = = = = = = = = = = = = = = = = = = the = = = = = = = = = = = = = = = = = = = = = = = = 11 read lock 11 ready to release lock 12 holds write locks 12 ready to release the write lockCopy the code

As you can see, the two are mutually exclusive and write and read alike

Write about

public static void main(String[] args) { ReentrantReadWriteLockDemo lockDemo = new ReentrantReadWriteLockDemo(); new Thread(()->{ lockDemo.write(); }).start(); new Thread(()->{ lockDemo.write(); }).start(); } = = = = = = = = = = = = = = = = = = = = = = = the = = = = = = = = = = = = = = = = = = = = = = = = 11 write lock 11 ready to release the write lock 12 holds write locks 12 ready to release the lockCopy the code

Also mutually exclusive, so we can conclude that writes are mutually exclusive, as long as they are written, they are mutually exclusive:

operation If the mutex
read no
Read and write is
Write about is
Write to read is

Jdk1.8 provides StampedLock (an improved read/write lock), which will be explored in more detail in the following sections.

LockSupport

In addition to using wait(), notify()/notifyall() to block and wake up threads, we can use the LockSupport tool.

LockSupport blocks and wakes up threads, while wait and notify are methods that lock objects.

Take a look at LockSupport source code

park()

public static void park() {
    UNSAFE.park(false, 0L);
}

Block duration can be set
public static void parkNanos(long nanos) {
    if (nanos > 0)
        UNSAFE.park(false, nanos);
}
Copy the code

upPark()

Wake up a thread
public static void unpark(Thread thread) {
    if(thread ! = null) UNSAFE.unpark(thread); }Copy the code

Park and unpark call native methods. In addition to locking the thread, it is important to note that Interrupt exceptions cannot be caught when blocking. You need to determine the state manually.

CAS

CAS (compare and swap), look at the name should be easy to understand, compare and replace. This is the technology that CPU processors can provide today, and the locks we used earlier, whether on locking objects or blocking threads, have a relatively high performance cost. CAS, on the other hand, does not require A lock. It is implemented by an address V, the original value A, the new value B, for example, A =0, we want to change to 1, we first need to know the memory address of A, and then compare whether it is 0, if it is changed to 1. If not, return the current value and then spin.

CAS is also used in object locking and AQS. The principle is the same as above. AQS will be explained later. In JAVA, there are some classes for atomic operations implemented through CAS, such as:

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
  • AtomicReference
  • .

In retrospect, we used synchronized to lock Integer for cargo calculation; now we use AtomicInteger for cargo calculation

static AtomicInteger n = new AtomicInteger(0); d# 10,000 computations per thread
static class AddThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            n.incrementAndGet();
        }
    }
}

public static void main(String[] args) {
    Start five threads
    for (int i = 0; i < 5; i++) {
        new AddThread().start();
    }

    while(Thread.activeCount() > 2) {
        Thread.yield();
    }

    System.out.println("n :"+ n.get()); } Result n :50000Copy the code

As you can see, we didn’t use synchronized, volatile, or lock, but the end result was correct. Other AtomicXXX use can be practiced by yourself.

conclusion

This chapter mainly introduces the Locking mechanism in JAVA, including synchronized, volatile, lock, CAS, etc., some of which are in-depth, and some of the main use (this time write a little more, try to break down the details, in-depth point), I hope to bring you some help. If there are written questions or need to discuss the comments can be corrected.