Java and contracting of Lock and Condition Detailed

Java is still the core of its implementation on the tube. Since the use of the tube, the bag may be implemented concurrently all tools. Concurrent programming field, there are two core issues: one is mutually exclusive , that is, the same time allowing only one thread to access shared resources; the other is synchronous , that is, how to communicate between threads, collaboration. These two issues are the tube can be resolved. Java SDK and contracting the tube is achieved by two Lock and Condition interfaces, Lock mutex to solve the problem, Condition for solving synchronization problems .

synchronized Java language itself provides the tube is an implementation process, since the Java language level has been achieved from the tube, then why provide another to achieve it in the SDK? Is Java Standards Committee also agreed that the program "repeat-create the wheel" of? Obviously there is a huge difference between them. Where is the difference then? If you can understand the problem in depth, you make good use of Lock helpful. Here we take a look at the analysis of this issue.

Reengineering the tube reasons

You may have heard many legends in this area, such as in the version of Java 1.5, synchronized performance than the SDK's Lock, but after the 1.6 version, synchronized done a lot of optimization, performance will be caught up, so after version 1.6 It was also recommended the use of synchronized. Whether that performance can justify the "repeat-create the wheel" in it? Obviously not. Because performance optimization problem click on it, completely unnecessary, "repeat-create the wheel."

Here, on this issue, if you can come up with a reason to it? If you're careful, you may be able to think a little. Destroy not seize condition program, but this program synchronized no solution. The reason is that when synchronized application of resources, if not eligible, the thread directly into the blocked state, while the thread into the blocked state, and consequently can not do, can not release resources thread has possession. But we hope that:

For "not to seize" this condition, the thread take up some resources to further apply for other resources, if not eligible, it may take the initiative to release the resources occupied, so this condition will not seize destroyed.

If we re-design a mutex to solve this problem, then how to design it? I think there are three options.

  1. Able to respond to interrupts . synchronized question is, after holding the lock A, B fail if you try to acquire a lock, then thread enters the blocked state, once a deadlock occurs, there would be no chance to wake up the blocked thread. But if the blocked thread to respond to the interrupt signal, that is to say when we sent the blocked thread interrupt signal, it can wake up, that it had a chance to release the lock held by A. This undermines the conditions can not be preempted.
  2. Support timeout . If the thread is not acquired over a period of time to lock, instead of entering the blocked state, but returns an error, then this thread have the opportunity to release the lock once held. This can also damage can not be preempted conditions.
  3. Non-blocking access to the lock . If the attempt to acquire the lock fails, it does not enter the blocked state, but directly returned, then this thread have the opportunity to release the lock once held. This can also damage can not be preempted conditions.

These three programs can fully compensate for the problem of synchronized. Here I believe you should be able to understand these three programs is the main reason "repeat-create the wheel", reflecting on the API, it is the three methods Lock interface. Details are as follows:

// 支持中断的API
void lockInterruptibly() 
  throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit) 
  throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();

How to ensure visibility

Lock using Java SDK inside, there is a classic example, is the try{}finally{}need to focus on is finally releasing locks inside. Needless to explain this example, you look at the following code to understand. But there is little need to explain, and that is how the visibility is guaranteed. You already know Java visibility in multi-threaded through Happens-Before the rules guarantee, and synchronized been able to guarantee the visibility, but also because there is a synchronized relevant rules: synchronized unlock Happens-Before subsequent addition of this lock lock. Java SDK that rely ensure visibility inside Lock it? In the following example the code, the thread of the T1 value was + = 1 operation, that subsequent thread T2 can see the correct value of the result of it?

class X {
  private final Lock rtl =
、  new ReentrantLock();
  int value;
  public void addOne() {
    // 获取锁
    rtl.lock();  
    try {
      value+=1;
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
}

The answer must be yes. Java SDK inside lock implementation is very complex, here I will not start elaborate, but the principle still needs a brief introduction: It is the use of volatile Happens-Before-related rules . Inside the Java SDK of ReentrantLock, the internal member variable holding a volatile state, when acquiring the lock, will read the value of the state; unlocking time, will read the value of state (code simplified as shown below). That is, before the implementation of value + = 1, the first program to read and write a volatile variable state, after the implementation of value + = 1, and write a volatile variable state. Happens-Before accordance with the relevant rules:

  1. Sequence rules : For thread T1, value + = unlock 1 Happens-Before operating the lock release ();
  2. volatile variable rules : Since the state = 1 will first read the state, so the thread T1 is unlock () operation lock Happens-Before thread T2 () operation;
  3. Transitivity rule : The thread T1 value + = 1 Happens-Before thread lock T2 () operation.
class SampleLock {
  volatile int state;
  // 加锁
  lock() {
    // 省略代码无数
    state = 1;
  }
  // 解锁
  unlock() {
    // 省略代码无数
    state = 0;
  }
}

Therefore, subsequent threads T2 can see the value of the correct result. 

What is reentrant lock

If you look carefully, you will find specific class name locks we create is ReentrantLock, this translation is called reentrant lock , in front of this concept we have not been introduced. The so-called reentrant lock, by definition, refers to the thread can repeatedly acquire the same lock . For example, the following code, when executed thread to ① at T1, RTL has acquired lock, when calling get () method ①, the locking operation is executed again lock ② rtl. At this point, if the lock is reentrant rtl, then thread T1 can be locked again successful; if rtl is not reentrant lock, then thread T1 at this time will be blocked.

In addition reentrant lock, you may also have heard of reentrant functions, reentrant function how to understand it? It refers to the thread can recall? Obviously not, so-called reentrant functions, means that multiple threads can simultaneously call this function , each thread can get the right result; supports the thread switching within a thread, no matter how many times is switched, the results are correct . Multiple threads can be executed at the same time, also supports thread switch, what does this mean? Thread safety ah. So, reentrant functions are thread-safe.

class X {
  private final Lock rtl =
、  new ReentrantLock();
  int value;
  public int get() {
    // 获取锁
    rtl.lock();         ②
    try {
      return value;
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
  public void addOne() {
    // 获取锁
    rtl.lock();  
    try {
      value = 1 + get(); ①
    } finally {
      // 保证锁能释放
      rtl.unlock();
    }
  }
}

Fair locking and non-locking fair

When using ReentrantLock, you will find ReentrantLock This class has two constructors, is a no-argument constructor is passed in a fair argument constructor. fair argument represents the lock fairness policy, if it means passing in true need to construct a fair lock, otherwise it means to construct a non-fair locks.

//无参构造函数:默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//根据公平策略参数创建锁
public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() 
                : new NonfairSync();
}

 Entrance waiting queue, lock corresponds to a waiting queue, if a thread does not get the lock, will enter the queue, when there is a thread releases the lock, you need to wake up a waiting thread from the waiting queue. If the lock is fair, who wake up strategy is a long time to wait, who will wake up, fair enough; if non-lock fair, the fair guarantee is not provided, it is possible to wait for a short time but the first thread to be awakened.

Best practices with locks

You already know that, although locks can solve a lot of concurrency issues, but the risk is also pricey. Could lead to a deadlock, it may also affect performance. Are there aspects of this have best practices related to it? There are also many. But I think the most recommended is the concurrent master Doug Lea "Java Concurrent Programming: Design Principles and Patterns" book, best practices recommended by three locks, they are:

  1. Always lock only when updating member variable objects
  2. Always lock only when you access the member variable variable
  3. Not always call the other object's methods when locked

Three rules, the first two is estimated that you will agree, the last one you might think it is too harsh. But I still prefer you to obey, because calls to other object methods, it is too unsafe, perhaps "other" calls the method which has threads sleep (), and there may be very unresponsive of I / O operations, these will seriously affect performance. Even more frightening is the method of "other" category may also be locked and double locked it could lead to a deadlock.

Concurrency issues, already difficult to diagnose, so you must let your code as safe, as simple as possible, even a little can go wrong, we should strive to avoid.

to sum up

Each method of concurrent Java SDK interfaces Lock bag inside, you can feel, is well thought of. In addition to support similar synchronized implicit lock locked outside () method also supports time-out, non-blocking, interruptible way to obtain locks, which are three ways for us to write more secure, robust concurrent program provides a great convenience. I hope you in the use of locks, we must consider very carefully.

In addition to master complicated by Doug Lea Recommended three best practice, you can also refer to some, such as: rules reduce lock hold time, reducing lock granularity and other industry well-known, in fact, they are essentially the same, but in place of the locked lock it. You can understand themselves, their summary, the final summed up its own set of best practices.

发布了337 篇原创文章 · 获赞 220 · 访问量 21万+

Guess you like

Origin blog.csdn.net/qq_33589510/article/details/104241637