Thread status diagram
Threads include the following 5 states.
1. New state: After the thread object is created, it enters the new state. For example, Thread thread = new Thread().
2. Runnable: Also known as "executable state". After the thread object is created, other threads call the object's start() method to start the thread. For example, thread.start(). A thread in a ready state may be scheduled to execute by the CPU at any time.
3. Running state (Running): The thread obtains CPU permissions for execution. It should be noted that threads can only enter the running state from the ready state.
4. Blocked state: Blocked state means that the thread gives up the CPU usage rights for some reason and temporarily stops running. It is not until the thread enters the ready state that it has a chance to go to the running state. There are three types of blockage:
(01) Waiting to block - By calling the thread's wait() method, let the thread wait for the completion of a certain work.
(02) Synchronized blocking--A thread fails to acquire synchronized synchronization lock (because the lock is occupied by other threads), it will enter a synchronized blocking state.
(03) Other blocking--The thread will enter a blocking state by calling sleep() or join() of the thread or issuing an I/O request. When the sleep() state timed out, join() waited for thread to terminate or timed out, or I/O processing was completed, the thread re-entered to the ready state.
5. Dead state: The thread has finished executing or exited the run() method due to an exception, and the thread ends its life cycle.
Implement multi-threading methods Thread and Runnable
Thread: Inherit the thread class, implement the run method, and call the start method in the main function to start the thread
Runnable: interface, implements the Runnable interface, passes it as a parameter to Thread constructor, and calls the start method in main
example:
class task implements Runnable { private int ticket = 10; @Override public void run() { for (int i = 0; i < 20; i++) { if (this.ticket > 0) { System.out.println(Thread.currentThread().getName() + " sold ticket " + this.ticket--); } } } } } }; public class RunnableTest { public static void main(String[]args){ task mytask = new task(); Thread t1 = new Thread(mytask); Thread t2 = new Thread(mytask); Thread t3 = new Thread(mytask); t1.start(); t2.start(); t3.start(); } }//ThreadTest.java Source code class MyThread extends Thread { private int ticket = 10; public void run() { for (int i = 0; i < 20; i++) { if (this.ticket > 0) { System.out.println(this.getName() + "Ticket Sell: ticket" + this.ticket--); } } }} public class ThreadTest { public static void main(String[] args) { // Start 3 threads t1, t2, t3; each thread sells 10 tickets each! MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); MyThread t3 = new MyThread(); t1.start(); t2.start(); t3.start(); }};
The difference between Thread and Runnable
Thread is a class, and Runnable is an interface; Thread itself is a class that implements the Runnable interface. We know that "a class can only have one parent class, but it can implement multiple interfaces", so Runnable has better scalability. In addition, Runnable can also be used for "sharing of resources". That is, multiple threads are created based on a certain Runnable object, and they will share resources on the Runnable object. Generally, it is recommended to implement multi-threading through "Runnable"!
Thread's run and start
start() : Its function is to start a new thread, and the new thread will execute the corresponding run() method. start() cannot be called repeatedly. start() actually starts the thread through the local method start0(). start0() will run a new thread, and the new thread will call the run() method.
run() : run() can be called repeatedly just like ordinary member methods. If you call run() separately, run() will be executed in the current thread, and the new thread will not be started! run() is to directly call the run() method of the Runnable member of the Thread thread, and will not create a new thread.
// Demo.java's source code class MyThread extends Thread{ public MyThread(String name) { super(name); } public void run(){ System.out.println(Thread.currentThread().getName()+" is running"); } }; public class Demo { public static void main(String[] args) { Thread mythread=new MyThread("mythread"); System.out.println(Thread.currentThread().getName()+" call mythread.run()"); mythread.run(); System.out.println(Thread.currentThread().getName()+" call mythread.start()"); mythread.start(); } }Output:
main call mythread.run()main is runningmain call mythread.start()mythread is running
synchronized
In Java, each object has a synchronization lock. When we call the object's synchronized method, the object lock is acquired, and synchronized (obj) acquires the synchronization lock of the "obj object". Different threads access to the synchronization lock is mutually exclusive. The synchronization lock of the object can only be acquired by one thread at a certain time. Through the synchronization lock, we can achieve mutually exclusive access to the "object/method" in multiple threads. For example, there are now two threads A and thread B, which will all access the "synchronized lock of object obj". Suppose that at some point, thread A acquires the "obj's synchronization lock" and performs some operations; at this time, thread B also attempts to acquire the "obj's synchronization lock" - Thread B will fail to acquire, it must wait until thread A releases the "obj's synchronization lock" and can only be run.
Basic Rules
Article 1: When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads will be blocked from access to the "synchronized method" or "synchronized code block" of "the object".
Article 2: When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads can still access the asynchronized code block of "this object".
Article 3: When a thread accesses the "synchronized method" or "synchronized code block" of "a certain object", other threads will be blocked from accessing other "synchronized methods" or "synchronized code block" of "the object".
synchronized method
public synchronized void foo1() { System.out.println("synchronized method");}synchronized code block public void foo2() { synchronized (this) { System.out.println("synchronized method"); }}This in the synchronized code block refers to the current object. This can also be replaced with other objects, such as this is replaced with obj, then foo2() acquires obj's synchronization lock when synchronized(obj).
Synchronized code blocks can control conflict-restricted access areas more accurately, and sometimes perform more efficiently
Instance lock and global lock
Instance Lock--Locked on an instance object. If the class is a singleton, then the lock also has the concept of a global lock. The synchronized keyword corresponds to the instance lock.
Global Lock-- This lock is targeted at a class. No matter how many objects the instance is, the threads share the lock. The global lock corresponds to static synchronized (or locked on the class or classloader object of this class).
pulbic class Something { public synchronized void isSyncA(){} public synchronized void isSyncB(){} public static synchronized void cSyncA(){} public static synchronized void cSyncB(){}}
(01) x.isSyncA() and x.isSyncB() cannot be accessed simultaneously. Because isSyncA() and isSyncB() are both synchronization locks that access the same object (object x)!
(02) x.isSyncA() and y.isSyncA() can be accessed at the same time. Because it is not accessing the synchronization lock of the same object, x.isSyncA() accesses the synchronization lock of x, while y.isSyncA() accesses the synchronization lock of y.
(03) x.cSyncA() and y.cSyncB() cannot be accessed simultaneously. Because cSyncA() and cSyncB() are both static types, x.cSyncA() is equivalent to Something.isSyncA(), and y.cSyncB() is equivalent to Something.isSyncB(), they share a synchronization lock and cannot be asked at the same time.
(04) x.isSyncA() and Something.cSyncA() can be accessed simultaneously. Because isSyncA() is an instance method, x.isSyncA() uses the lock of object x; while cSyncA() is a static method, Something.cSyncA() can understand that it is a "class lock" used. Therefore, they can be accessed simultaneously.
Thread blocking and wake-up wait, notify, notifyAll
In Object.java, interfaces such as wait(), notify() and notifyAll() are defined. The function of wait() is to let the current thread enter a waiting state, and wait() will also let the current thread release the lock it holds. The role of notify() and notifyAll() is to wake up the waiting thread on the current object; notify() is to wake up a single thread, while notifyAll() is to wake up all threads.
The API details about waiting/wake in the Object class are as follows:
notify() -- Wake up a single thread waiting on this object monitor.
notifyAll() -- Wake up all threads waiting on this object monitor.
wait() -- Put the current thread in a "wait (blocking) state" and "until other threads call the notify() method or notifyAll() method of this object", and the current thread is awakened (entered to the "ready state").
wait(long timeout) -- Put the current thread in a "wait (blocking) state" and "until other threads call the object's notify() method or notifyAll() method, or exceed the specified amount of time", and the current thread is awakened (entered to the "ready state").
wait(long timeout, int nanos) -- Let the current thread be in a "wait (blocking) state", "until other thread calls the object's notify() method or notifyAll() method, or some other thread interrupts the current thread, or has exceeded a certain amount of time", and the current thread is awakened (entered to the "ready state").
// WaitTest.java's source code class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName()+" call notify()"); // Wake up the current wait thread notify(); } }} public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { // Start "thread t1" System.out.println(Thread.currentThread().getName()+" start t1"); t1.start(); // The main thread waits for t1 to wake up through notify(). System.out.println(Thread.currentThread().getName()+" wait()"); t1.wait(); System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } } }}Output
main start t1main wait()t1 call notify()main continue
(01) Note that the "main thread" in the figure represents "main thread main". "Thread t1" represents "thread t1" started in WaitTest. And "lock" represents "synchronous lock of the object t1".
(02) "Main thread" creates new "thread t1" through new ThreadA("t1"). Then, the "synchronous lock of the t1 object" is obtained through synchronized(t1). Then call t1.start() to start "thread t1".
(03) "Main thread" executes t1.wait() to release "lock of t1 object" and enters "wait (blocking) state". Wait for threads on t1 objects to wake it up via notify() or notifyAll().
(04) After "thread t1" is run, the "lock of the current object" is obtained through synchronized(this); then call notify() to wake up the "waiting thread on the current object", that is, wake up the "main thread".
(05) After "thread t1" is completed, release the "lock of the current object". Immediately afterwards, the "main thread" acquires the "lock of the t1 object" and then runs.
t1.wait() is the wait() method called through "thread t1", but the place where t1.wait() is called is in "main thread main". The main thread must be the "current thread", that is, the running state, before t1.wait() can be executed. Therefore, the "current thread" at this time is the "main thread main"! Therefore, t1.wait() is to make the "main thread" wait, not "thread t1"!
package thread.Test; public class NotifyAllTest { private static Object obj = new Object(); public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); ThreadA t2 = new ThreadA("t2"); ThreadA t3 = new ThreadA("t3"); t1.start(); t2.start(); t3.start(); try { System.out.println(Thread.currentThread().getName()+" sleep(3000)"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj) { System.out.println(Thread.currentThread().getName()+" notifyAll()"); obj.notifyAll();//Wake up t1.t2.t3 here } } static class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public void run() { synchronized (obj) { try { // Printout result System.out.println(Thread.currentThread().getName() + " wait"); // Release the obj object lock obj.wait(); // Printout result System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } Output:
t1 waitmain sleep(3000)t3 waitt2 waitmain notifyAll()t2 continue3 continue1 continue
(01) 3 threads "t1", "t2" and "t3" were created and started in the main thread.
(02) The main thread sleeps for 3 seconds through sleep(3000). During the main thread sleep for 3 seconds, we assume that the three threads "t1", "t2" and "t3" are all running. Take "t1" as an example. When it runs, it will execute obj.wait() to wait for other threads to wake it up through notify() or nofityAll(); by the same token, "t2" and "t3" will also wait for other threads to wake them up through nofity() or nofityAll().
(03) The main thread sleeps for 3 seconds and then runs. Execute obj.notifyAll() to wake up the waiting thread on obj, that is, wake up the three threads "t1", "t2" and "t3". Immediately after the main thread's synchronized(obj) is run, the main thread releases the "obj lock". In this way, "t1", "t2" and "t3" can obtain the "obj lock" and continue to run!
notify, notifyall and lock relationship
Functions such as wait(), notify() in Object, like synchronized, will operate on "object synchronization lock".
wait() will make the "current thread" wait. Because the thread enters the waiting state, the thread should release the "synchronous lock" held by its lock, otherwise other threads will not be able to obtain the "synchronous lock" and will not be able to run!
OK, after the thread calls wait(), it will release the "synchronous lock" held by its lock; and, according to the previous introduction, we know that the waiting thread can be awakened by notify() or notifyAll(). Now, please think about a question: What is notify() based on awakening the waiting thread? Or, what is the correlation between wait() and notify()? The answer is: based on "object synchronization lock".
The thread responsible for waking up the waiting thread (we call it "wake up thread"), can only wake up the waiting thread after obtaining the "synchronous lock of this object" (the synchronization lock here must be the same as the synchronization lock of the waiting thread) and calling the notify() or notifyAll() methods. Although, the waiting thread is awakened; however, it cannot be executed immediately because the wake-up thread still holds "synchronous lock for the object". You must wait until the wake-up thread releases the "Object's synchronization lock" before you can obtain the "Object's synchronization lock" and continue to run.
In short, notify(), wait() relies on "synchronous lock", which is held by object locks, and each object has and only one! This is why functions such as notify(), wait() are defined in the Object class, not in the Thread class.
Thread concessions yield
Thread concessions make the thread change from the execution state to the ready state, so that other waiting threads with the same priority can obtain execution rights; however, it is not guaranteed that after the current thread calls yield(), other threads with the same priority will definitely obtain execution rights; it is also possible that the current thread enters the "running state" and continues to run.
yield and wait
(01) wait() is to let the thread enter the "wait (blocking) state" from the "running state", while not yield() is to let the thread enter the "ready state" from the "running state".
(02) wait() is a synchronization lock that will thread release the object it holds, while the yield() method will not release the lock.
(03) wait is the method of object, yield is the method of Thread
Thread sleep
The function of sleep() is to let the current thread sleep, that is, the current thread will enter from the "running state" to the "sleep (blocking) state". sleep() will specify the sleep time, and the thread sleep time will be greater than/equal to the sleep time; when the thread is awakened again, it will change from a "blocking state" to a "ready state", waiting for the CPU to be scheduled to execute.
The difference between sleep and wait
The function of wait() is to allow the current thread to enter the "wait (blocking) state from the "running state" and also release the synchronization lock. The function of sleep() is to let the current thread enter the "sleep (blocking) state from the "running state". (This is actually not much different)
wait() releases the object's synchronization lock, while sleep() does not release the lock
wait is the method of object, sleep is the method of Thread
Join
Let the main thread wait, and the child thread can continue to run after the main thread is completed.
interrupt
Used to terminate a blocked thread
@Overridepublic void run() { try { while (true) { // Execute the task... } } catch (InterruptedException ie) { // Due to an InterruptedException exception, exit the while(true) loop and the thread terminates! }}In while(true), the call to interrupt() of the thread generates an InterruptException interrupt. The interrupted capture is outside while(true), thus exiting the while(true) loop
Terminates a thread in running state
@Overridepublic void run() { while (!isInterrupted()) { // Execute task... }}Common way to terminate threads
@Overridepublic void run() { try { // 1. isInterrupted() guarantees that the thread will be terminated as long as the interrupt is marked true. while (!isInterrupted()) { // Execute task... } } catch (InterruptedException ie) { // 2. InterruptedException exception guarantees that when an InterruptedException exception occurs, the thread is terminated. }}
Thread priority
The thread priority range in java is 1 to 10, and the default priority is 5. "High priority threads" will precede execution over "low priority threads". There are two types of threads in java: user thread and daemon thread. They can be distinguished by isDaemon() method: if false is returned, it means that the thread is a "user thread"; otherwise it is a "daemon thread". User threads generally perform user-level tasks, while daemon threads are also "backend threads", which are generally used to perform background tasks. It should be noted that the Java virtual machine will exit after the "user thread" is completed.
Each thread has a priority. "High priority threads" will precede execution over "low priority threads". Each thread can be marked as a daemon or non-daemon. When creating a new child thread in some running main thread, the child thread's priority is set to equal "priority of the main thread that created it", and "the child thread will be the daemon thread" when and only if "the main thread that created it is a daemon thread".
When a Java virtual machine is started, there is usually a single non-daemon thread (this thread is started through the main() method). The JVM will run until any of the following conditions occur, and the JVM will terminate the run:
(01) The exit() method is called, and exit() has permission to be executed normally.
(02) All "non-daemon threads" are dead (that is, there are only "daemon threads" in the JVM).
Daemon
(01) The main thread main is the user thread, and the child thread t1 it creates is also the user thread.
(02) t2 is the daemon thread. When the "main thread main" and "sub-thread t1" (they are both user threads) are executed and only the daemon thread t2 is left, the JVM automatically exits.
Producer and consumer issues
(01) The producer only produces when the warehouse is not full, and stops production when the warehouse is full.
(02) Consumers can only consume when they have products in storage, and wait if they have empty warehouses.
(03) When consumers find that there is no product to consume in the warehouse, they will notify the producer.
(04) When producers produce consumable products, they should notify the waiting consumers to consume.
Communication between threads
Method: Shared memory and messaging
Shared memory: Thread A and thread B share memory, thread A updates the value of the shared variable, refreshes it to the main memory, and thread B goes to the main memory to read the updated variables of thread A. The entire communication process must pass through main memory. Synchronization is performed explicitly.
If a variable is of type volatile, the read and write of the variable will be atomic. If it is multiple volatile operations or composite operations similar to volatile++, these operations are not atomic in their entirety.
The volatile variable itself has the following characteristics:
[Visibility]: When reading a volatile variable, you can always see the last write to the volatile variable (any thread).
[Atomicity]: It has atomicity for the read/write of any single volatile variable, but it does not have atomicity for compound operations similar to volatile++.
volatile write: When writing a volatile variable, JMM will flush the shared variable in the local memory corresponding to the thread to the main memory.
volatile read: When reading a volatile variable, JMM will invalidate the local memory corresponding to the thread. The thread will next read the shared variable from the main memory.
Message delivery: The sending of a message is performed implicitly before the message is accepted.
ThreadLocal
ThreadLocal is not used to solve the problem of multi-thread access to shared objects. Generally speaking, the object to the thread through ThreadLocal.set() is an object used by the thread itself. Other threads do not need to be accessed and cannot be accessed. ThreadLocal allows each thread to maintain its own independent object. It is not implemented through ThreadLocal.set(), but an object created by the operation of the new object in each thread. Each thread creates one, not a copy or copy of the object. The reference to the newly created object is saved to each thread's own map through ThreadLocal.set(). Each thread has such a map. When ThreadLocal.get() is executed, each thread takes out the object placed in from its own map. Therefore, what is taken out is the object in each thread. The ThreadLocal instance is used as the map key. If the thing that ThreadLocal.set() enters is the same object shared by multiple threads, then the ThreadLocal.get() of multiple threads still obtains the shared object itself, and there is still a concurrent access problem.
An implementation of ThreadLocal
import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Class using ThreadLocal* * @author leizhimin 2010-1-5 10:35:27 */ public class MyThreadLocal { //Define a ThreadLocal variable to save int or Integer data private com.lavasoft.test2.ThreadLocal<Integer> tl = new com.lavasoft.test2.ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public Integer getNextNum() { //Get the value of tl and add 1, and update the value of t1 tl.set(tl.get() + 1); return tl.get(); } } class ThreadLocal<T> { private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>()); public ThreadLocal() { } protected T initialValue() { return null; } public T get() { Thread t = Thread.currentThread(); T obj = map.get(t); if (obj == null && !map.containsKey(t)) { obj = initialValue(); map.put(t, obj); } return obj; } public void set(T value) { map.put(Thread.currentThread(), value); } public void remove() { map.remove(Thread.currentThread()); } }In fact, ThreadLocal does this:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }