summary
The example of each Thread class represents a Thread, and the process is multi tasking at the operating system level, and the JVM runs in one process. Therefore, threads should be considered more in Java. The memory of a process can be shared by multiple threads.
Using threads is basically to make full use of cpu resources.
Status of the thread
Looking at the Java source code, we can see that there are six thread states: new, running, blocking, waiting, timeout waiting and termination. That is, new, runnable, blocked, waiting, timed waiting, and terminated.
- New: when a thread is just created, it does not call the start method and has not been included in the thread scheduling. At this time, it is in the new state.
- Runnable: runnable and running in Java are collectively referred to as runnable. At this time, the start method (runnable) is called to get the cpu time slice and then run (running). When the thread scheduler selects a thread from the runnable pool as the current thread, the thread is in the state. This is also the only way for a thread to enter the running state.
- Blocked: blocked status, blocked by lock. Is the state of a thread blocking when entering a synchronized keyword decorated method or code block (obtaining a lock).
- Waiting: the thread in this state needs the operation of other threads, such as notification or interrupt. Threads in this state will not be allocated CPU execution time. They will wait to be explicitly awakened, otherwise they will be in the state of waiting indefinitely.
- Timed waiting: compared with waiting, this status can be returned automatically after a user-defined time. There is no need to wait indefinitely for other threads to wake up. After a certain time, they will wake up automatically.
- Terminated: indicates that the thread has completed execution. When the thread's run() method completes, or the main() method of the main thread completes, we consider it terminated. The thread object may be alive, but it is no longer a separate thread. Once a thread terminates, it cannot regenerate. Calling the start() method on a terminated thread will throw a Java Lang.illegalthreadstateexception exception.

Create thread
There are two methods. One is to inherit the Thread class, but if the class itself has inherited other classes, it must implement the runnable interface.
Thread itself is an implementation of the Runnable interface.
We should implement the run method, that is, thread logic.
What is a lock
Simply put, a lock is used to control the access behavior in the case of multithreading. It can be understood as a license, which can be executed only after obtaining the license.
Data is easy to be read and written inconsistently under concurrent access. For example, before the write thread finishes writing variables, the read thread comes to access, resulting in incorrect accessed data. Therefore, speed up the read-write thread and do not release the lock before completing the task. At this time, other threads cannot read and write variables to ensure atomicity.
What is a reentry lock
ReentrantLock is a class that implements the lock interface and supports reentry. The thread will be blocked after being locked twice by lock. In order to avoid this situation in complex call scenarios, there is a reentrant lock. As long as the lock and unlock times are the same.
Difference between reentrant lock and synchronized
The performance is almost the same as that of synchronized, but ReentrantLock has richer functions, supports fair locks and unfair locks, and is more suitable for concurrent scenarios
It's easier to use synchronized. You don't need to lock and unlock manually. It's all done implicitly. ReentrantLock needs to be unlocked manually, and the unlocking operation should be placed in the finally code block as far as possible. The two operations are unbalanced and easy to deadlock. Add the parameter true to implement a fair lock, not a non fair lock.
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
The core function of reentry lock is delegated to the internal class sync implementation, and FairSync and NonfairSync are implemented according to whether the lock is fair or not. This is a typical strategy model.
Principle of reentrant implementation
There is a state variable state in the AbstractQueuedSynchronizer object. A state of 0 indicates that the lock is idle, greater than 0 indicates that it is occupied, and the value indicates the number of times the current thread is repeatedly occupied.
private volatile int state;
Then implement a simple lock as follows:
final void lock() { // compareAndSetState is a CAS operation on the state. If the modification is successful, the lock will be occupied if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else //If the modification is not successful, it indicates that other threads have used the lock, so you may need to wait acquire(1); }
acquire():
public final void acquire(int arg) { //tryAcquire() attempts to acquire the lock again, //If it is found that the lock is occupied by the current thread, update the state to indicate the number of repeated occupation, //At the same time, announce the success, which is the key to reentry if (!tryAcquire(arg) && // If the acquisition fails, join the team here and wait acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //If it is interrupted during waiting, reset the interrupt flag bit selfInterrupt(); }
Fair lock and unfair lock
By default, reentry locks are unfair. When multiple threads compete for locks, they do not come in order, but are obtained randomly. If the first competition fails for a non fair lock, it will enter the waiting queue like a fair lock. Fair locks are obtained in a first come, first served order, but there is a performance loss.
It can also be understood as follows: a fair lock means that when the lock is available, the thread waiting on the lock for the longest time will obtain the right to use the lock. Non fair locks are randomly assigned this right of use.
From the code point of view:
//Unfair lock final void lock() { //Come up, no matter three, seven, twenty-one, just grab it if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else //If you can't grab it, go into the queue and wait slowly acquire(1); } //Fair lock final void lock() { //Go straight into the queue and wait acquire(1); }
Fair locks can avoid the problem of hunger and contention, and threads will not repeatedly obtain locks. If there is a hunger problem, a thread may not acquire a lock for a long time.
In terms of choice, unfair locks are used in most cases.
Lock acquisition time limit wait
When using synchronized to implement a lock, the thread blocking on the lock will wait until it obtains the lock, that is, the behavior of waiting indefinitely to obtain the lock cannot be interrupted. ReentrantLock provides us with a wait method trylock() for obtaining the lock limit. You can pass in the time parameter. No parameter means that the result of the lock application is returned immediately. Compared with lock(), it avoids the situation of infinite waiting.
Construct deadlock scenario: create two sub threads, which will attempt to obtain two locks respectively when running. One thread obtains lock 1 and then lock 2, and the other thread does the opposite. If there is no external interrupt, the program will be in a deadlock state and can never stop. We end the meaningless wait between threads by interrupting one of the threads. The interrupted thread will throw an exception, and another thread will be able to get the lock and end normally.
public class ReentrantLockTest { static Lock lock1 = new ReentrantLock(); static Lock lock2 = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new ThreadDemo(lock1, lock2));//The thread acquires lock 1 first and then lock 2 Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//The thread acquires lock 2 first and then lock 1 thread.start(); thread1.start(); } static class ThreadDemo implements Runnable { Lock firstLock; Lock secondLock; public ThreadDemo(Lock firstLock, Lock secondLock) { this.firstLock = firstLock; this.secondLock = secondLock; } @Override public void run() { try { while(!lock1.tryLock()){ TimeUnit.MILLISECONDS.sleep(10); } while(!lock2.tryLock()){ lock1.unlock(); TimeUnit.MILLISECONDS.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); } finally { firstLock.unlock(); secondLock.unlock(); System.out.println(Thread.currentThread().getName()+"Normal end!"); } } } }
The thread obtains the lock by calling the tryLock() method. When it fails to obtain the lock for the first time, it will sleep for 10 milliseconds, and then obtain it again until it succeeds. When the second acquisition fails, first release the first lock, then sleep for 10 milliseconds, and then try again until it succeeds. When a thread fails to obtain the second lock, it will release the first lock, which is the key to solving the deadlock problem. It avoids two threads holding one lock and then requesting another lock from each other.
Using condition
Condition is the companion object of reentry lock. It provides a mechanism of waiting and notification based on reentry lock. You can use the newCondition() method to generate a condition object:
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();

Using synchronized in combination with the wait and notify methods on the Object can realize the wait notification mechanism between threads, and ReentrantLock in combination with the Condition interface can also realize this function. And compared with the former, it is clearer and simpler to use.
How to use condition?
Before using the condition interface, you must call the lock() method of ReentrantLock to obtain the lock. The await() that calls the Condition interface will release the lock and wait on the Condition until the other thread invokes the signal() method of Condition to wake the thread. The usage is similar to wait and notify.
public class ConditionTest { static ReentrantLock lock = new ReentrantLock(); static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { lock.lock(); new Thread(new SignalThread()).start(); System.out.println("Main thread waiting for notification"); try { condition.await(); } finally { lock.unlock(); } System.out.println("The main thread resumes running"); } static class SignalThread implements Runnable { @Override public void run() { lock.lock(); try { condition.signal(); System.out.println("Child thread notification"); } finally { lock.unlock(); } } } }
Operation results:
Main thread waiting for notification Child thread notification The main thread resumes running
Implement a blocking queue
Implemented using condition,
Blocking queue is a special first in first out queue. It has the following characteristics: 1. Incoming and outgoing threads are safe. 2. When the queue is full, incoming threads will be blocked; When the queue is empty, the outgoing thread is blocked.
Blocking queue:
public class MyBlockingQueue<E> { int size;//Maximum capacity of blocking queue ReentrantLock lock = new ReentrantLock(); LinkedList<E> list=new LinkedList<>();//Queue underlying implementation Condition notFull = lock.newCondition();//Wait condition when queue is full Condition notEmpty = lock.newCondition();//Wait condition when queue is empty public MyBlockingQueue(int size) { this.size = size; } public void enqueue(E e) throws InterruptedException { lock.lock(); try { while (list.size() ==size)//Queue full, waiting on notFull condition notFull.await(); list.add(e);//Join the team: join the end of the linked list System.out.println("Join the team:" +e); notEmpty.signal(); //Notifies the thread waiting on the notEmpty condition } finally { lock.unlock(); } } public E dequeue() throws InterruptedException { E e; lock.lock(); try { while (list.size() == 0)//Queue is empty, waiting on notEmpty condition notEmpty.await(); e = list.removeFirst();//Out of line: remove the first element of the list System.out.println("Out of the team:"+e); notFull.signal();//Notifies the thread waiting on the notFull condition return e; } finally { lock.unlock(); } } }
Test code:
public static void main(String[] args) throws InterruptedException { MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(2); for (int i = 0; i < 10; i++) { int data = i; new Thread(new Runnable() { @Override public void run() { try { queue.enqueue(data); } catch (InterruptedException e) { } } }).start(); } for(int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { try { Integer data = queue.dequeue(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
Operation results:

In order to make everyone better understand the use of reentry lock. Now let's implement a simple counter using a reentry lock. This counter can ensure the accuracy of statistics in a multithreaded environment. Please see the following example code:
public class Counter { //Reentry lock private final Lock lock = new ReentrantLock(); private int count; public void incr() { // When accessing count, you need to lock it lock.lock(); try { count++; } finally { lock.unlock(); } } public int getCount() { //Reading data also requires locking to ensure data visibility lock.lock(); try { return count; }finally { lock.unlock(); } } }
summary
ReentrantLock is a reentrant exclusive lock. Compared with synchronized, it has richer functions, supports fair lock implementation, interrupt response and time limited waiting, etc. You can easily implement the waiting notification mechanism in conjunction with one or more Condition conditions.
- For the same thread, reentry lock allows you to obtain a lock repeatedly, but the number of times to apply for and release the lock must be the same.
- By default, reentry locks are unfair, and the performance of fair reentry locks is worse than that of unfair locks
- The internal implementation of reentry lock is based on CAS operation.
- The accompanying object Condition of reentry lock provides the functions of await() and singal(), which can be used for message communication between threads.