In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >
Share
Shulou(Shulou.com)06/01 Report--
This article is to share with you about the introductory process of java concurrent programming, the editor thinks it is very practical, so I share it with you to learn. I hope you can get something after reading this article.
1. Introduction 1.1.Two ways to implement threads package chapter2;/** * @ author calebzhao * 14:05 on 2019-6-29 * / public class MyThreadDemo1 {public static void main (String [] args) {new Thread1 () .start (); new Thread (new Thread2 ()) .start () }} class Thread1 extends Thread {@ Override public void run () {System.out.println (Thread.currentThread (). GetName ());}} class Thread2 implements Runnable {@ Override public void run () {System.out.println (Thread.currentThread (). GetName ());} 1.2, thread state (life cycle) 1.2.1, thread 5 states
1. New (NEW): a new thread object has been created.
two。 Runnable (RUNNABLE): after the thread object is created, other threads (such as the mainthread) call the object's start () method. The thread in this state is in the runnable thread pool, waiting to be selected by thread scheduling to obtain the right to use cpu.
3. RUNNING: the runnable state (runnable) thread gets the cpu time slice (timeslice) and executes the program code.
4. BLOCKED: blocking state means that the thread gives up the right to use cpu for some reason, that is, it gives up the cpu timeslice and stops running temporarily. It is not until the thread enters the runnable (runnable) state that it has a chance to get the cpu timeslice to the running state again. There are three types of congestion:
(1). Wait blocking: the thread running (running) executes the o.wait () method, and JVM places the thread in the waiting queue (waitting queue). (2) Synchronization blocking: when a thread running (running) acquires an object's synchronization lock, if the synchronization lock is occupied by another thread, JVM will put the thread into the lock pool (lock pool). (III)。 Other blocking: when a thread running (running) executes the Thread.sleep (long ms) or t.join () method, or when an Icano request is issued, JVM puts the thread in a blocking state. When the sleep () state times out, the join () waits for the thread to terminate or times out, or when IBG O has finished processing, the thread returns to the runnable (runnable) state.
5. DEAD: the thread ends its life cycle when the execution of the run (), main () method ends, or the run () method exits because of an exception. Dead threads cannot be resurrected again.
1.2.2, thread running state diagram
1)。 Initial state
You can get a thread class by implementing the Runnable interface and inheriting Thread. When an instance of new comes out, the thread enters its initial state.
2)。 Runnable state
The runnable state only says that you are qualified to run, and if the scheduler does not pick you, you will always be runnable.
Call the start () method of a thread, which enters a runnable state.
The current thread sleep () method ends, other threads join () ends, waiting for the user to finish typing, a thread gets the object lock, and these threads will also enter a runnable state.
The current thread time slice is used up, the current thread's yield () method is called, and the current thread enters a runnable state.
The thread in the lock pool gets the object lock and enters the runnable state.
3)。 Running state
The state in which the thread is in when the thread scheduler selects a thread from the runnable pool as the current thread. This is also the only way for a thread to get into a running state.
4)。 Death state
When the thread's run () method completes, or the main thread's main () method completes, we think it is dead. This thread object may be alive, but it is no longer a separate thread of execution. Once a thread dies, it cannot come back to life.
Calling the start () method on a dead thread throws a java.lang.IllegalThreadStateException exception.
5)。 Blocking state
The current thread T calls the Thread.sleep () method, and the current thread enters a blocking state.
The other thread T2 running in the current thread calls the join () method, and the current thread enters a blocking state.
While waiting for user input, the current thread enters a blocking state.
6)。 Wait queue (is a method in Object, but affects threads)
Before calling the wait (), notify () methods of obj, you must acquire the obj lock, that is, you must write it in the synchronized (obj) code snippet.
Steps and diagrams related to waiting queues
Thread 1 acquires the lock of object An and is using object A.
Thread 1 calls the wait () method of object A.
Thread 1 releases the lock on object An and immediately enters the waiting queue.
The object in the lock pool competes for the lock of object A.
Thread 5 acquires the lock of object A, enters the synchronized block, and uses object A.
Thread 5 calls the notifyAll () method of object A to wake up all threads and enter the lock pool. | Thread 5 calls the notify () method of object A to wake up a thread. I don't know who it will wake up, and the awakened thread enters the lock pool.
The synchronized where the notifyAll () method is located ends, and thread 5 releases the lock on object A.
The threads in the lock pool compete for the object lock, but we don't know when thread 1 will get it. | the original lock pool + the threads awakened in step 6 compete for object locks together.
7)。 Lock pool statu
When the current thread wants to call the synchronization method of object A, it finds that the lock of object An is occupied by another thread, and the current thread enters the lock pool state. In short, the lock pool is full of threads that want to compete for object locks.
When one thread 1 is awakened by another thread 2, thread 1 enters the lock pool state to compete for the object lock.
Lock pool is a concept that exists only in a synchronous environment, with an object corresponding to a lock pool.
1.3. Comparison of sleep, yield, join and wait
Thread.sleep (long millis), which must be the current thread calling this method, the current thread enters blocking, but does not release the object lock, and the thread automatically wakes up to the runnable state after millis. Purpose: the best way to give other threads a chance to execute.
Thread.yield (), which must be the current thread calling this method, the current thread abandons the acquired cpu time slice and changes from the running state to the runnable state, allowing OS to select the thread again. Purpose: let threads of the same priority take turns, but there is no guarantee that they will take turns. In practice, there is no guarantee that yield () will achieve the purpose of concession, because the concessionary thread may be selected again by the thread scheduler. Thread.yield () does not cause blocking.
T.join () / t.join (long millis), the current thread calls the join method of other thread 1, the current thread blocks, but does not release the object lock, until thread 1 finishes execution or the millis time is up, the current thread enters the runnable state.
Obj.wait (), the current thread calls the object's wait () method, and the current thread releases the object lock and enters the waiting queue. Rely on notify () / notifyAll () wake up or wait (long timeout) timeout time to wake up automatically.
Obj.notify () wakes up a single thread waiting on this object monitor, and the choice is arbitrary. NotifyAll () wakes up all threads waiting on this object monitor.
1.4.Thread.sleep (long time)
The Thread.sleep (long time) method sleeps the currently executing thread (giving up the CPU time slice) for the specified number of milliseconds.
It is important to note that:
When calling the sleep () method, if the current thread holds the lock, it does not cause the current thread to release the lock.
Does the sleep not release the lock thread entering the blocking state or the ready state?
The answer is to enter the blocking state, which means that Thread is in the TIMED_WAITING state of Java (but this state is not that important and can be thought of as the internal details of java, so users don't have to worry too much about it). Next level, the implementation details of the underlying sleep on different OS are different. But basically it is to suspend the current thread and then set a signal or clock interrupt to wake up. Thread after sleep does not consume any CPU until it is woken up (to be exact, most OS will be implemented, unless some OS implementation is lazy). At this point, wait has about the same effect on the current thread, pausing scheduling and waiting for notify or a timeout. During this period, CPU will not be consumed.
2. Multithreaded implementation of Bank call queuing 2.1 version 1package chapter2.version1;/** * @ author calebzhao * 2019-6-29 8:58 * / public class Bank {public static void main (String [] args) {TicketWindow ticketWindow = new TicketWindow ("window 1"); TicketWindow ticketWindow2 = new TicketWindow ("window 2"); TicketWindow ticketWindow3 = new TicketWindow ("window 3"); ticketWindow.start () TicketWindow2.start (); ticketWindow3.start ();}} package chapter2.version1;/** * @ author calebzhao * 8:58 on 2019-6-29 * / public class TicketWindow extends Thread {private static int MAX = 50; private static int current = 1; private String name; public TicketWindow (String name) {this.name = name;} @ Override public void run () {while (current)
< MAX){ System.out.println("窗口:" + name +" 当前叫号:" + current); current++; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }} 此版本中注意TicketWindow 类的成员变量current 为static, 表示无论实例化这个类多少次,都共享同一份变量,因为这个变量是在类加载的时候就已经创建好的。为了让多个线程消耗同一个current 所以才定义为static的,不然每个线程的current都是各自的,与其他线程不相关。 以上代码把业务与线程紧密掺杂在一起,为了让多个线程访问同一份current把他定义为static明显是不合适的。 2.2 版本2package chapter2.version2;/** * @author calebzhao * 2019/6/29 8:58 */public class Bank { public static void main(String[] args) { // 有线程安全问题, 多个线程同时访问到current变量 TicketWindow ticketWindow = new TicketWindow(); Thread windowThread1 = new Thread(ticketWindow, "1号窗口"); Thread windowThread2 = new Thread(ticketWindow, "2号窗口"); Thread windowThread3 = new Thread(ticketWindow, "3号窗口"); windowThread1.start(); windowThread2.start(); windowThread3.start(); } } package chapter2.version2;/** * @author calebzhao * 2019/6/29 9:02 */public class TicketWindow implements Runnable { private int MAX = 50; private int current = 1; @Override public void run() { while (current < MAX){ System.out.println("窗口:" + Thread.currentThread().getName() +" 当前叫号:" + current); current++; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } 这里TicketWindow 并不是继承Thread, 这样启动线程多次线程可以共享同一份TicketWindow 实例, 把业务逻辑与线程启动拆分清晰明了。 注:以上2个版本均存在线程安全性问题,这是由于current变量在run()方法可能被多个线程同时访问, 可能多个线程同时执行到 System.out.println("窗口:" + Thread.currentThread().getName() +" 当前叫号:" + current); 这行代码,导致不同的线程打印出了一样的叫号值,比如运行以上程序会输出如下结果:3. Daemon thread
Important point: the daemon thread will exit with the exit of the parent thread, and the daemon thread is suitable for some auxiliary work, but the core working thread cannot be set as the daemon thread.
Package chapter2;/** * @ author calebzhao * 14:07 on 2019-6-29 * / public class DeemonThreadDemo {public static void main (String [] args) {Thread thread = new Thread () {@ Override public void run () {while (true) {System.out.println ("hello");} / / set to daemon thread thread.setDaemon (true); thread.start (); System.out.println ("main thread exit");}}
From the code, we can see that the code in thread has a while (true) {} statement. In our image, while (true) is an endless loop and should never quit and print hello forever, but it can be seen from the output that there is no longer printing after running for a period of time, indicating that the parent thread ends execution and the child thread exits at random.
4. Thread.join ()
Thread.join () function: the thread that calls the join () method will not continue to execute until its own execution is finished.
Thread.join (time) function: the thread that calls the join (time) method will wait until it executes the specified time himself, then it will continue to execute later
4.1 unlimited waiting for package chapter2;/** * @ author calebzhao * 14:32 on 2019-6-29 * / public class ThreadJoinDemo {public static void main (String [] args) throws InterruptedException {Thread thread1 = new Thread (()-> {for (int I = 0; I)
< 500; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } }); Thread thread2 = new Thread(() ->{for (int I = 0; I)
< 500; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("所有线程已结束执行"); }} thread1 和thread2交互执行, 而main线程的打印结果一定是在2个线程全部执行完后才打印结果。 4.2 等待指定时间package chapter2;/** * @author calebzhao * 2019/6/29 14:32 */public class ThreadJoinDemo2 { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() ->{System.out.println (Thread.currentThread (). GetName () + "start execution"); try {Thread.sleep (3000);} catch (InterruptedException e) {e.printStackTrace ();} System.out.println (Thread.currentThread (). GetName () + "end execution");}) Thread1.start (); / / although join occurs, it only waits for the specified time, not thread1.join (1000); System.out.println ("the main thread waits for 1000 milliseconds to execute without waiting for the thread1 to finish");}}
5. Several ways to interrupt threads 5.1. Introduce package chapter2;import org.junit.Test;/** * @ author calebzhao * 14:53 on 2019-6-29 * / public class ThreadInterrupt {public static void main (String [] args) {Thread thread = new Thread (()-> {while (true) {try {Thread.sleep (1000)) } catch (InterruptedException e) {System.out.println ("thread interrupted"); e.printStackTrace (); / / exit while loop after interruption to control thread exit break;}) Thread.start (); System.out.println (thread.isInterrupted ()); / / interrupt the sleep of the thread thread so that thread1 no longer continues sleep thread.interrupt (); System.out.println (thread.isInterrupted ()); System.out.println ("main thread exit");} @ Test public void test2 () {Object MONITOR = new Object () Thread thread = new Thread (()-> {while (true) {synchronized (MONITOR) {try {MONITOR.wait (100);} catch (InterruptedException e) {System.out.println ("thread interrupted" + Thread.interrupted ()) E.printStackTrace (); thread.start (); System.out.println (thread.isInterrupted ()); thread.interrupt (); System.out.println (thread.isInterrupted ()); System.out.println ("main thread exit") } @ Test public void test3 () {Thread thread1 = new Thread (()-> {System.out.println (Thread.currentThread (). GetName () + "start execution"); while (true) {}}); Thread main = Thread.currentThread () Thread thread2 = new Thread (()-> {try {Thread.sleep (1000);} catch (InterruptedException e) {e.printStackTrace () } / / currently the mainthread is waiting for the thread1 thread, so you need to interrupt the main thread so that the main thread can stop waiting and continue to execute main.interrupt (); System.out.println ("interrupt");}); thread1.start (); thread2.start () Try {/ / main thread waits for thread 1 thread to finish execution before continuing to execute thread1.join ();} catch (InterruptedException e) {e.printStackTrace ();} System.out.println ("main thread executes to");}} 5.2.Control package chapter2;import org.junit.Test through whilte (thread) {} variable / * * @ author calebzhao * 15:34 on 2019-6-29 * / public class ThreadCloseDemo {class Worker implements Runnable {private volatile boolean running = true @ Override public void run () {/ / there is a problem here. If doSomething () is very time-consuming, the next execution of while (running) does not have a chance to be executed, and cannot immediately interrupt while (running) {/ / if the doSomething () method} System.out.println ("exit endless loop") } public void shutdown () {this.running = false;}} @ Test public void close1 () {Worker worker = new Worker (); Thread thread = new Thread (worker); thread.start (); try {Thread.sleep (1000);} catch (InterruptedException e) {e.printStackTrace () } worker.shutdown (); System.out.println ("main thread execution completed");}}
The execution result of test1 ():
5.3.Control package chapter2;import org.junit.Test;/** * @ author calebzhao * 2019-6-29 15:34 * / public class ThreadCloseDemo2 {class Worker2 implements Runnable {private volatile boolean running = true; @ Override public void run () {while (true) {if (Thread.currentThread (). IsInterrupted ()) {break } / / there is a problem here. If doSomething () is very time-consuming, the above code has no chance to be executed and cannot immediately interrupt / / doSomething ()} System.out.println ("exit the endless loop");} @ Test public void close2 () {Worker2 worker = new Worker2 () Thread thread = new Thread (worker); thread.start (); try {Thread.sleep (1000);} catch (InterruptedException e) {e.printStackTrace ();} thread.interrupt (); System.out.println ("main thread finished");}}
Package chapter2;import org.junit.Test;/** * @ author calebzhao * 16:06 on 2019-6-29 * / public class ThreadCloseDemo2 {class ThreadService {private Thread executeThread; private boolean running = true; public void execute (Runnable task) {executeThread = new Thread (()-> {Thread workerThread = new Thread (task); workerThread.setDaemon (true) WorkerThread.start () / / try {/ / Let executeThread wait for workerThread to finish execution before executing / / if workerThread is time-consuming, because the daemon thread is attached to the parent thread, if there is no join here, / / then it is possible that the executeThread thread will finish execution before the workerThread is really finished. Caused workerThread to quit workerThread.join (). } catch (InterruptedException e) {/ / e.printStackTrace ();} / / executeThread thread is interrupted to wait or workerThread normal execution ends this.running = false;}); executeThread.start () } public void shutdown (long timeout) {long beginTime = System.currentTimeMillis (); / / workerThread is still executing while (running) {/ / to determine whether the waiting timeout if (System.currentTimeMillis ()-beginTime > = timeout) {/ / has timed out. Interrupting executeThread waiting will make this.running = false. Upon execution, / / the completion of executeThread execution causes the workerThread daemon thread to exit executeThread.interrupt (); / / exit the while loop break;} try {Thread.sleep (1) } catch (InterruptedException e) {/ / indicates that the main thread was interrupted by e.printStackTrace (); break;} public boolean isRunning () {return this.running }} / * must wait 3 seconds before finishing execution * / @ Test public void test1 () {ThreadService threadService = new ThreadService (); threadService.execute (()-> {/ / this is a very time-consuming operation while (true) {})) / / Let the thread interrupt execution immediately after 2 seconds: long begin = System.currentTimeMillis (); threadService.shutdown (3000); long end = System.currentTimeMillis (); System.out.println ("time consuming:" + (end-begin)); System.out.println ("main thread execution result") } / * end with the execution of threadService without waiting for 3 seconds (after 1 second the subsequent code of the main thread can be executed) * / @ Test public void test2 () {ThreadService threadService = new ThreadService (); threadService.execute (()-> {try {Thread.sleep (1000)) } catch (InterruptedException e) {e.printStackTrace ();}}); / / Let the thread interrupt execution immediately after 2 seconds long begin = System.currentTimeMillis (); threadService.shutdown (3000); long end = System.currentTimeMillis (); System.out.println ("time consuming:" + (end-begin)) System.out.println ("main thread execution result");} 5.5, interrupt (), interrupted (), isInterrupted () difference and principle 5.5.1, conclusion
Interrupt () method: used to interrupt a thread, the state of the thread calling this method will be set to the "interrupted" state. Note: thread interrupts simply set the interrupt status bit of the thread and will not stop the thread. It is necessary for the user to monitor the status of the thread and do the processing. Methods that support thread interruptions (that is, methods that throw InterruptedException after an interrupt, such as Thread.sleep, Object.wait, etc.) are monitoring the interrupt state of a thread. Once the interrupt state of a thread is set to "interrupt state", an interrupt exception is thrown and the interrupt state of the thread is set to false.
Interrupted (): returns whether the thread is in the interrupted state and clears the interrupted state
IsInterrupted (): returns whether the thread is in an interrupted state
5.5.2, principle
The following content comes from https://my.oschina.net/itblog/blog/787024
Public class Interrupt {public static void main (String [] args) throws Exception {Thread t = new Thread (new Worker ()); t.start (); Thread.sleep (200); t.interrupt (); System.out.println ("Main thread stopped.") } public static class Worker implements Runnable {public void run () {System.out.println ("Worker started."); try {Thread.sleep } catch (InterruptedException e) {System.out.println ("Worker IsInterrupted:" + Thread.currentThread () .isInterrupted ()) } System.out.println ("Worker stopped.");}
The content is simple: the main thread main starts a child thread Worker, then lets worker sleep on 500ms and main sleeps on 200ms, and then main calls the worker thread's interrupt method to interrupt the status of the print interrupt after the worker,worker is interrupted. The following is the implementation result:
Worker started.Main thread stopped.Worker IsInterrupted: falseWorker stopped.
The Worker is clearly interrupted, but the isInterrupted () method returns false. Why?
After searching around on stackoverflow, I found that some netizens mentioned that you can view the JavaDoc (or source code) that throws the InterruptedException method, so I checked the documentation of the Thread.sleep method. This is how the InterruptedException exception is described in doc:
InterruptedException-if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
Notice the latter sentence, "when this exception is thrown, the interrupt state has been cleared." So the isInterrupted () method should return false. But sometimes we need the isInterrupted method to return true. What should we do? Here's the difference between interrupt, interrupted and isInterrupted:
The interrupt method is used to interrupt a thread, and the state of the thread calling the method is set to the "interrupted" state. Note: thread interrupts simply set the interrupt status bit of the thread and will not stop the thread. The user is required to monitor the status of the thread and do the processing. Methods that support thread interruptions (that is, methods that throw InterruptedException after an interrupt, such as sleep and Object.wait here) are monitoring the interrupt state of a thread, and an interrupt exception is thrown once the thread's interrupt state is set to "interrupt state". This view can be confirmed by this article:
Let's look at the implementation of the interrupted method:
Public static boolean interrupted () {return currentThread () .isInterrupted (true);}
And the implementation of isInterrupted
Public boolean isInterrupted () {return isInterrupted (false);}
One of these two methods is static and the other is not, but they are actually calling the same method, except that the parameter passed in by the interrupted method is true and the parameter passed in by iInterrupted is false. So what exactly does this parameter mean? Take a look at the implementation of the isInterrupted (boolean) method:
/ * Tests if some Thread has been interrupted. The interrupted state * is reset or not based on the value of ClearInterrupted that is * passed. * / private native boolean isInterrupted (boolean ClearInterrupted)
This is a native method, it doesn't matter if you can't see the source code, the parameter name ClearInterrupted has clearly expressed the role of the parameter-whether to clear the interrupt state. The comment on the method also clearly states that "the interrupt state will be reset based on the value of the ClearInterrupted parameter passed in." Therefore, the static method interrupted will clear the interrupt state (the parameter ClearInterrupted passed in is true), while the instance method isInterrupted will not (the parameter ClearInterrupted passed in is false). Going back to the question: obviously, if you want the isInterrupted method to return true, you can restore the interrupted state by calling the interrupt () method again before calling the isInterrupted method:
Public class Interrupt {public static void main (String [] args) throws Exception {Thread t = new Thread (new Worker ()); t.start (); Thread.sleep (200); t.interrupt (); System.out.println ("Main thread stopped.") } public static class Worker implements Runnable {public void run () {System.out.println ("Worker started."); try {Thread.sleep } catch (InterruptedException e) {Thread curr = Thread.currentThread (); / / call the interrupt method to interrupt yourself again, setting the interrupt state to "interrupt" curr.interrupt () System.out.println ("Worker IsInterrupted:" + curr.isInterrupted ()); System.out.println ("Worker IsInterrupted:" + curr.isInterrupted ()); System.out.println ("Static Call:" + Thread.interrupted ()) / / clear status System.out.println ("- After Interrupt Status Cleared-"); System.out.println ("Static Call:" + Thread.interrupted ()); System.out.println ("Worker IsInterrupted:" + curr.isInterrupted ()) System.out.println ("Worker IsInterrupted:" + curr.isInterrupted ());} System.out.println ("Worker stopped.");}
Execution result:
Worker started.Main thread stopped.Worker IsInterrupted: trueWorker IsInterrupted: trueStatic Call: true-After Interrupt Status Cleared-Static Call: falseWorker IsInterrupted: falseWorker IsInterrupted: falseWorker stopped.
You can also see from the execution results that the first two calls to the isInterrupted method return true, indicating that the isInterrupted method does not change the interrupt state of the thread, while the next call to the static interrupted () method returns true for the first time, indicating that the thread is interrupted and false for the second time, because the interrupt state has been cleared on the first call. The last two calls to the isInterrupted () method will definitely return false.
So, in what scenario do we need to interrupt the thread (reset the interrupted state) in the catch block?
The answer is: if you cannot throw InterruptedException (like the Thread.sleep statement here is placed in the run method of Runnable, which does not allow any checked exceptions), but want to tell the upper caller that an interrupt has occurred, you can only reset the interrupt state in catch.
Public class TaskRunner implements Runnable {private BlockingQueue queue; public TaskRunner (BlockingQueue queue) {this.queue = queue;} public void run () {try {while (true) {Task task = queue.take (10, TimeUnit.SECONDS); task.execute () } catch (InterruptedException e) {/ / Restore the interrupted status Thread.currentThread () .interrupt ();}
So the question is: why clear the interrupted state when throwing an InterruptedException?
There is no official explanation for this question, and it is estimated that only Java designers can answer it. But the explanation here seems reasonable: an interrupt should only be handled once (you catch the InterruptedException, which means you can handle the exception, and you don't want the upper caller to see the interrupt).
Reference address:
Https://my.oschina.net/itblog/blog/787024
Http://stackoverflow.com/questions/7142665/why-does-thread-isinterrupted-always-return-false
Http://stackoverflow.com/questions/2523721/why-do-interruptedexceptions-clear-a-threads-interrupted-status
Http://www.ibm.com/developerworks/library/j-jtp05236/
Http://blog.csdn.net/z69183787/article/details/25076033
6. Synchronized6.1, sample code package chapter2;/** * @ author calebzhao * 17:11 on 2019-6-29 * / public class SynchronizedDemo {private static final Object LOCK = new Object (); public static void main (String [] args) {Runnable runnable = ()-> {synchronized (LOCK) {System.out.println (Thread.currentThread (). GetName () + "executing") Try {Thread.sleep (60000000);} catch (InterruptedException e) {e.printStackTrace ();} System.out.println (Thread.currentThread (). GetName () + "execution ends");}}; Thread thread1 = new Thread (runnable) Thread thread2 = new Thread (runnable); Thread thread3 = new Thread (runnable); thread1.start (); thread2.start (); thread3.start (); System.out.println ("end of main thread execution");} 6.2.View via jconsole
The following interface is displayed by entering the jconsole command in cmd:
Select the java program just running in the local process and enter the main interface
You can see the three threads running by Thread-0, Thread-1 and Thread-2. Click on the right side of the corresponding thread to see "owner Thread-0".
6.3.View via jps and jstack
After starting the program:
Step 1: enter the jps command to view the currently running java process
Step 2: enter jstack [pid] to see that there are three threads Thread-1, Thread-2 are in the BLOCKED (on object nonitor) state, and Thread-0 is in the TIMED_WATING (sleepint) state (because of Thread.sleep ())
6.4.View assembly instructions through the javap-c [xxxx.class] command
6.5 this lock
Meaning: means to add the keyword synchronized to the method
Package chapter2;/** * @ author calebzhao * 17:56 on 2019-6-29 * / public class ThisLockDemo {public static void main (String [] args) {ThisLock thisLock = new ThisLock (); Thread thread1 = new Thread (()-> {thisLock.m1 ();}); Thread thread2 = new Thread (()-> {thisLock.m2 ();}); thread1.start () Thread2.start (); System.out.println ("main thread ends execution");}} class ThisLock {public synchronized void M1 () {System.out.println (Thread.currentThread (). GetName () + "M1 start execution"); try {Thread.sleep (10000000);} catch (InterruptedException e) {e.printStackTrace () } System.out.println (Thread.currentThread (). GetName () + "M1 end execution");} public synchronized void m2 () {System.out.println (Thread.currentThread (). GetName () + "m2 start execution"); try {Thread.sleep (10000000);} catch (InterruptedException e) {e.printStackTrace () } System.out.println (Thread.currentThread (). GetName () + "m2 end execution");}}
Running result:
You can see that the execution of Thread-1 m2 must wait for the execution of Thread-0 M1 before it can be executed, indicating that the lock used by the same class to add the synchronized keyword to the method is a this lock
6.6 class lock
Introduction: the key lock used to add synchronized to the static method is the class lock
Package chapter2;/** * @ author calebzhao * 18:29 on 2019-6-29 * / public class ClassLockDemo {public static void main (String [] args) {ClassLock classLock1 = new ClassLock (); ClassLock classLock2 = new ClassLock (); Thread thread1 = new Thread (()-> {classLock1.m1 ();}); Thread thread2 = new Thread (()-> {classLock2.m2 () }); thread1.start (); thread2.start (); System.out.println ("main thread ends execution");}} class ClassLock {public static synchronized void M1 () {System.out.println (Thread.currentThread (). GetName () + "M1 start execution"); try {Thread.sleep (10000000) } catch (InterruptedException e) {e.printStackTrace ();} System.out.println (Thread.currentThread (). GetName () + "M1 end execution");} public static synchronized void m2 () {System.out.println (Thread.currentThread (). GetName () + "m2 start execution"); try {Thread.sleep (10000000) } catch (InterruptedException e) {e.printStackTrace ();} System.out.println (Thread.currentThread (). GetName () + "m2 end execution");}}
You can see that the execution of Thread-1 m2 must wait until the execution of Thread-0 M1 is completed, indicating that the lock used by multiple instances of the same class to add the synchronized keyword to the static method is a class lock.
6.7 deadlock package chapter2;/** * @ author calebzhao * 18:33 on 2019-6-29 * / public class DieLockDemo {public static void main (String [] args) {DieLock dieLock = new DieLock (); Thread thread1 = new Thread (()-> {dieLock.doSomething1 ();}); Thread thread2 = new Thread (()-> {dieLock.doSomething2 ();}) Thread1.start (); thread2.start (); System.out.println ("main thread finished");}} class DieLock {private final Object LOCK1 = new Object (); private final Object LOCK2 = new Object (); public void doSomething1 () {synchronized (LOCK1) {try {/ / represents a time-consuming operation Thread.sleep } catch (InterruptedException e) {e.printStackTrace ();} System.out.println (Thread.currentThread (). GetName () + "get LOCK1, wait for LOCK2"); synchronized (LOCK2) {System.out.println ("doSomething1") } public void doSomething2 () {synchronized (LOCK2) {try {/ / represents a time-consuming operation Thread.sleep (100);} catch (InterruptedException e) {e.printStackTrace () } System.out.println (Thread.currentThread () .getName () + "get LOCK2, wait for LOCK1"); synchronized (LOCK1) {System.out.println ("doSomething2");}
6.8. Synchronzied lock reentry
The keyword synchronized has the function of lock reentry, that is, when using synchronized, when a thread acquires an object lock and requests the object lock again, it can acquire the lock of the object. When other synchronized methods / blocks of this class are called within a synchronized method / block, the lock can always be obtained.
Package chapter2;/** * @ author calebzhao * 15:43 on 2019-9-1 * / public class SynchronizedReentrantDemo {public synchronized void methodA () {System.out.println ("methodA invoked"); this.methodB ();} public synchronized void methodB () {System.out.println ("methodB invoked");} public static void main (String [] args) {SynchronizedReentrantDemo demo = new SynchronizedReentrantDemo (); demo.methodA () }}
You can see that it is also possible to call methodB () modified by synchronized in the synchronized method methodsA, even if methodA () has not released the this lock, which proves that the synchronzied lock can be reentered.
6.9. The principle of synchronized
The lock implementation of synchronized in Java includes bias lock, lightweight lock and heavy lock (long waiting time).
An instance of an object consists of three parts: object header, instance variable, and fill data.
Object header: the basis for implementing synchronized's lock object
Instance variable: stores the attribute data information of the class, including the property information of the parent class. If the instance part of the array also includes the length of the array, this part of the memory is aligned by 4 bytes.
Fill data: because the virtual machine requires that the starting address of the object must be an integral multiple of 8 bytes. Padding data is not necessary, just for byte alignment, which is fine.
The lock object used by synchronized is stored in the Java object header. Two words are used in jvm to store the object header (if the object is an array, three words are allocated, and the extra word records the length of the array). Its main structure is composed of Mark Word and Class Metadata Address, and its structure is shown in the following table:
By default, Mark Word stores the HashCode, generational age, lock marks, etc. The following is the Mark Word default storage structure of 32-bit JVM
Because the information of the object header is an additional storage cost that has nothing to do with the data defined by the object itself, taking into account the space efficiency of JVM, Mark Word is designed as a non-fixed data structure to store more effective data. It will reuse its own storage space according to the state of the object itself. For example, under 32-bit JVM, in addition to the Mark Word default storage structure listed above, there are also the following structures that may change:
Lightweight locks and bias locks are newly added after Java 6 optimizes synchronized locks, which we will briefly analyze later. Here we mainly analyze the heavyweight lock, which is usually called the synchronized object lock, with the lock identifier bit 10, where the pointer points to the starting address of the monitor object (also known as the pipe or monitor lock). Each object has a monitor associated with it, and there are many ways to implement the relationship between the object and its monitor. For example, monitor can be created and destroyed with the object or automatically generated when a thread tries to acquire an object lock, but when a monitor is held by a thread, it is locked. In the Java virtual machine (HotSpot), monitor is implemented by ObjectMonitor, and its main data structure is as follows (located in the HotSpot virtual machine source code ObjectMonitor.hpp file, implemented by C++)
ObjectMonitor () {_ header = NULL; _ count = 0; / number of records _ waiters = 0, _ recursions = 0; _ object = NULL; _ owner = NULL; _ WaitSet = NULL; / / threads in wait state will be added to _ WaitSet _ WaitSetLock = 0; _ Responsible = NULL; _ succ = NULL _ cxq = NULL; FreeNext = NULL; _ EntryList = NULL; / / threads waiting for lock block will be added to the list _ SpinFreq = 0; _ SpinClock = 0; OwnerIsThread = 0;}
There are two queues in ObjectMonitor, _ WaitSet and _ EntryList, which are used to hold the list of ObjectWaiter objects (each thread waiting for a lock is encapsulated as an ObjectWaiter object). _ owner points to the thread that holds the ObjectMonitor object. When multiple threads access a piece of synchronous code at the same time, they first enter the _ EntryList collection. When the thread gets the monitor of the object, it enters the _ Owner area and sets the owner variable in monitor to the counter count plus 1 in the monitor of the current thread. If a thread calls the wait () method, it restores the currently held monitor,owner variable to null,count minus 1, while the thread enters the WaitSe t collection waiting to be woken up. If the current thread finishes executing, it also releases the monitor (lock) and resets the value of the variable so that other threads enter and acquire the monitor (lock). As shown in the following figure
From this point of view, the monitor object exists in the object header of each Java object (the point of the stored pointer), and the synchronized lock acquires the lock in this way, which is why any object in Java can be used as a lock, and it is also the reason why methods such as notify/notifyAll/wait exist in the top-level object Object (which will be analyzed later). Ok~, has the above knowledge base. Next we will further analyze the specific semantic implementation of synchronized at the bytecode level.
6.10. The underlying implementation principle of synchronized
In order to analyze the underlying principle, we write the simplest class with the following code:
Package chapter2;/** * @ author calebzhao * @ date 18:41 on 2019-11-30 * / public class SynchronizedDemo2 {public synchronized static void test1 () {System.out.println ("test1");} public synchronized void test2 () {System.out.println ("test2");} public void test3 () {synchronized (this) {System.out.println ("test4") 6.10.1. The underlying principle of synchronized method:
Method-level synchronization is implicit, that is, it does not need to be controlled by bytecode instructions, it is implemented in method calls and return operations. JVM can tell whether a method is synchronized by the ACC_SYNCHRONIZED access flag in the method table structure (method_info Structure) in the method constant pool. When a method is called, the invocation instruction will check whether the method's ACC_SYNCHRONIZED access flag is set, and if so, the execution thread will first hold the monitor (the term pipe is used in the virtual machine specification), then execute the method, and finally release the monitor when the method is completed (whether normal or abnormal). During method execution, the thread of execution holds the monitor, and no other thread can get the same monitor. If an exception is thrown during the execution of a synchronous method and the exception cannot be handled within the method, the monitor held by the synchronous method is automatically released when the exception is thrown outside the synchronous method. Let's take a look at how the bytecode level is implemented:
By executing javap-v SynchroizedDemo2.class, the following bytecode instructions are output:
Bytecode instructions corresponding to the test1 () method:
Bytecode instructions corresponding to the test2 () method:
As can be seen from the bytecode, the synchronized-modified method does not have monitorenter instructions and monitorexit instructions, but is indeed replaced by the ACC_SYNCHRONIZED logo, which indicates that the method is a synchronous method. JVM uses the ACC_SYNCHRONIZED access flag to identify whether a method is declared as a synchronous method, so as to execute the corresponding synchronous call. This is the basic principle that synchronized locks are implemented on synchronous code blocks and synchronous methods. At the same time, we must also note that in the early version of Java, synchronized is a heavy lock and inefficient, because the monitor lock (monitor) depends on the Mutex Lock of the underlying operating system, and the operating system needs to switch from user state to kernel state when switching between threads. The transition between this state takes a relatively long time and the time cost is relatively high. This is why the early synchronized was inefficient. Fortunately, after Java 6, Java officials have greatly optimized synchronized from the JVM level, so now the synchronized lock efficiency is also optimized very well. After Java 6, in order to reduce the performance consumption caused by acquiring locks and releasing locks, lightweight locks and biased locks are introduced. Next, we will take a brief look at the Java official optimization of synchronized locks in the JVM layer.
6.10.2. The underlying implementation principle of synchonized code block:
Bytecode instructions corresponding to the test3 () method:
JSR133's explanation:
The synchronized statement requires a reference to an object; it then attempts to perform a lock action on the object's tube and waits if the lock action fails to complete successfully. When the lock action executes successfully, the code in the synchronized statement block is run. Once the execution of the code in the statement block ends, whether normal or abnormal, a lock action is automatically executed on the pipe that previously performed the unlock action.
The synchronized method automatically performs a lock action when called. The method body is not executed until the lock action completes successfully. In the case of an instance method, the lock is the pipe associated with the instance that called the method (that is, the this during the execution of the method body). In the case of a static method, the lock is the Class object corresponding to the class that defines the method. Once the method body execution finishes, whether normal or abnormal, a lock action is automatically executed on the pipe where the unlock action was previously performed.
It can be seen from the bytecode that the implementation of the synchronous statement block uses monitorenter and monitorexit instructions, in which the monitorenter instruction points to the start position of the synchronous code block, and the monitorexit instruction indicates the end position of the synchronous code block. when executing the monitorenter instruction, the current thread will try to acquire the ownership of the monitor corresponding to the objectref (that is, the object lock). When the entry counter of the monitor of the objectref is 0, the thread can successfully obtain the monitor and set the counter value to 1. The lock was taken successfully. If the current thread already has ownership of objectref's monitor, it can reenter the monitor (reentrant will be analyzed later), and the value of the counter will be incremented by 1. If other threads already have ownership of objectref's monitor, the current thread will be blocked until the executing thread finishes execution, that is, the monitorexit instruction is executed, and the executing thread will release the monitor (lock) and set the counter value to 0, and the other threads will have a chance to hold the monitor. It is worth noting that the compiler will ensure that no matter how the method is completed, every monitorenter instruction called in the method executes its corresponding monitorexit instruction, regardless of whether the method ends normally or abnormally. In order to ensure that the monitorenter and monitorexit instructions can still be paired correctly when the method exception completes, the compiler automatically generates an exception handler that declares that it can handle all exceptions, and its purpose is to execute monitorexit instructions. You can also see an extra monitorexit instruction in the bytecode, which is the instruction to release monitor that is executed at the end of the exception.
6.11. Optimization of synchronized by Java virtual machine
There are a total of four lock states, no lock state, bias lock, lightweight lock, and heavy lock. With the competition of locks, locks can be upgraded from biased locks to lightweight locks, and then upgraded to heavy locks, but the upgrade of locks is one-way, that is to say, only from low to high, there will be no degradation of locks. We have analyzed the heavy locks in detail before, and we will introduce bias locks and lightweight locks as well as other optimization methods of JVM. Here, we do not intend to go deep into the implementation and conversion process of each lock. It is more about the core optimization idea of each lock provided by the Java virtual machine. After all, the specific process is rather tedious. If you need to understand the detailed process, you can refer to "in-depth understanding of the principle of the Java virtual machine".
6.11.1. Bias lock
Biased lock is a new lock added after Java 6. It is an optimization method for locking operation. Through research, it is found that in most cases, the lock not only does not exist multi-thread competition, but is always acquired by the same thread many times. Therefore, in order to reduce the cost of acquiring locks by the same thread (involving some CAS operations, time-consuming), biased locks are introduced. The core idea of the biased lock is that if a thread acquires the lock, the lock enters the biased mode, and the structure of the Mark Word becomes biased lock structure. When the thread requests the lock again, it does not need to do any synchronous operation, that is, the process of acquiring the lock, which saves a lot of lock application operations and provides the performance of the program. Therefore, for situations where there is no lock competition, the bias lock has a good optimization effect, after all, it is very likely that the same thread applies for the same lock many times in a row. However, for situations with fierce lock competition, the biased lock will fail, because it is very likely that the thread applying for the lock will be different each time, so the biased lock should not be used in this situation, otherwise the loss will outweigh the gain. It should be noted that after the failure of the biased lock, it will not immediately expand to a heavy lock, but will first be upgraded to a lightweight lock. Let's move on to lightweight locks.
6.11.2. Lightweight lock
If the bias lock fails, the virtual machine will not immediately upgrade to a heavy lock, it will also try to use an optimization method called lightweight lock (added after 1.6), and the structure of the Mark Word will also become the structure of a lightweight lock. Lightweight locks can improve program performance on the basis that "there is no competition for most locks throughout the synchronization cycle". Note that this is empirical data. It is important to understand that lightweight locks adapt to situations where threads execute synchronous blocks alternately, and if there is a situation where the same lock is accessed at the same time, it will cause the lightweight lock to expand to a heavy lock.
6.11.3. Spin lock
After the lightweight lock fails, in order to prevent the thread from actually hanging at the operating system level, the virtual machine will also carry out an optimization method called spin lock. This is based on the fact that in most cases, the thread will not hold the lock for too long, and it may outweigh the gain if you directly suspend the thread at the operating system level. after all, the operating system needs to switch from user mode to kernel state when switching between threads, and the transition between this state takes a relatively long time, and the time cost is relatively high, so spin locking will assume that the current thread can acquire the lock in the near future. Therefore, the virtual machine allows the current thread that wants to acquire the lock to do several empty loops (which is also called spin), usually not for long, perhaps 50 or 100 loops, after several loops, if you get the lock, you can successfully enter the critical area. If the lock cannot be acquired, the thread will be suspended at the operating system level, which is how the spin lock is optimized, which can indeed improve efficiency. In the end, we have no choice but to upgrade to a heavyweight lock.
6.11.4. Lock elimination
Eliminating lock is another lock optimization of virtual machine, which is more thorough. When Java virtual machine compiles JIT (which can be simply understood as compiling when a piece of code is about to be executed for the first time, it is also called instant compilation), by scanning the running context, it is possible to remove locks that cannot compete for shared resources. In this way, unnecessary locks can be eliminated, which can save meaningless request lock time. The append of the following StringBuffer is a synchronous method, but the StringBuffer in the add method is a local variable and will not be used by other threads, so there can be no competition for shared resources in StringBuffer, and JVM will automatically remove its locks.
Public class StringBufferRemoveSync {public void add (String str1, String str2) {/ / StringBuffer is thread safe. Since sb can only be used in the append method and cannot be referenced by other threads / / so sb is a resource that cannot be shared, JVM automatically eliminates the internal lock StringBuffer sb = new StringBuffer (); sb.append (str1) .append (str2) } public static void main (String [] args) {StringBufferRemoveSync rmsync = new StringBufferRemoveSync (); for (int I = 0; I
< 10000000; i++) { rmsync.add("abc", "123"); } }}7、wait、notify方法7.1、含义(需仔细研读) wait()、notify()是Object类的方法, 调用这2个方法前,执行线程必须已经获得改对象的对象锁,即只能在同步方法或同步代码块中调用wait() 或者notify()方法,如果调用这2个方法时没有获得对象锁将会抛出IllegalMonitorStateException异常 调用wait()方法后,当前线程立即释放已经获得的锁,并且将当前线程置入"预执行队列(WaitSet)中", 并且在wait()所在的代码处停止执行,必须直到收到notify()方法的通知或者被中断执行当前线程才能被唤醒继续往wait()方法后面的代码执行 notitify()方法用来通知那些等待获取该对象锁的线程, 如果有多个线程等待,则由线程规划器随机挑选出一个处于wait状态的线程B,对其发出Notify通知,使B退出等待队列,处于就绪状态,被重新唤醒的线程B会尝试获取临界区的对象锁,被唤醒线程B在真正获取到锁后就会继续执行wait()后面的代码。需要说明的是,在执行notify()方法后,并不会使当前线程A马上释放对象锁,处于wait状态的线程B也不能马上获取对象锁,要等到执行notify()方法的线程A将程序执行完,也就是退出synchronized代码块后,当前线程A才会释放对象锁,但是释放之后并不代表线程B就一定会获取到对象锁,只是说此时A、B都有机会竞争获取到对象锁 如果notify()方法执行时,此时并没有任何线程处于wait状态,那么执行该方法相当于无效操作 notify()与notifyAll()的区别是:notify()方法每次调用时都只是从所有处于wait状态的线程中随机选择一个线程进入就绪状态,而notifyAll()则是使所有处于wait状态的线程全部退出等待队列,全部进入就绪状态,此处唤醒不等于所有线程都获得该对象的monitor,此时优先级最高的那个线程优先执行(获得对象锁),但也有可能是随机执行(获得对象锁),这要取决于jvm实现 7.2、生产者消费者模式7.2.1、 版本1产生假死(全部进入等待wait状态)package chapter2;import java.util.stream.Stream;/** * @author calebzhao * 2019/6/30 7:44 */public class ProducerAndConsumerVersion1 { private final Object LOCK = new Object(); private boolean isProduced; private int num = 0; public static void main(String[] args) { ProducerAndConsumerVersion1 version1 = new ProducerAndConsumerVersion1(); Stream.of("P1", "P2", "P3", "P4").forEach((item) ->{new Thread (()-> {while (true) {version1.produce ();}, item) .start ();}) Stream.of ("C1") .forEach (item-> {new Thread (()-> {while (true) {version1.consumer ();}}, item). Start (); System.out.println ("main thread execution ends") } public void produce () {synchronized (LOCK) {if (isProduced) {try {System.out.println ("[" + Thread.currentThread (). GetName () + "] produce wait"); LOCK.wait (); System.out.println ("[" + Thread.currentThread (). GetName () + "] produce wait after") } catch (InterruptedException e) {e.printStackTrace ();}} else {num++; System.out.println ("[" + Thread.currentThread (). GetName () + "] P = = >" + num); isProduced=true; LOCK.notify () System.out.println ("[" + Thread.currentThread (). GetName () + "] notify after");} public void consumer () {synchronized (LOCK) {if (isProduced) {System.out.println ("[" + Thread.currentThread (). GetName () + "] C = >" + num) IsProduced = false; LOCK.notify (); System.out.println ("[" + Thread.currentThread (). GetName () + "] notify after");} else {try {System.out.println ("[" + Thread.currentThread (). GetName () + "] consumer wait") LOCK.wait (); System.out.println ("[" + Thread.currentThread (). GetName () + "] consumer wait after");} catch (InterruptedException e) {e.printStackTrace ();}
The above code execution never ends, and all threads end up in a wait state (no deadlock occurs)
7.2.2, correct version of package chapter2;import java.util.stream.Stream;/** * @ author calebzhao * 2019-6-30 7:44 * / public class ProducerAndConsumerVersion2 {private final Object LOCK = new Object (); private boolean isProduced; private int num = 0; public static void main (String [] args) {ProducerAndConsumerVersion2 version1 = new ProducerAndConsumerVersion2 () Stream.of ("P1", "P2", "P3", "P4"). ForEach ((item)-> {new Thread (()-> {while (true) {version1.produce ();}}, item). Start ();}) Stream.of ("C1", "C2", "C3", "C4") .forEach (item-> {new Thread (()-> {while (true) {version1.consumer ();}}, item). Start (); System.out.println ("main thread execution ends") } public void produce () {synchronized (LOCK) {while (isProduced) {try {System.out.println ("[" + Thread.currentThread (). GetName () + "] produce wait"); LOCK.wait (); System.out.println ("[" + Thread.currentThread (). GetName () + "] produce wait after") } catch (InterruptedException e) {e.printStackTrace ();}} num++; System.out.println ("[" + Thread.currentThread (). GetName () + "] P = >" + num); isProduced=true; LOCK.notifyAll () System.out.println ("[" + Thread.currentThread (). GetName () + "] notify after");}} public void consumer () {synchronized (LOCK) {while (! isProduced) {try {System.out.println ("[" + Thread.currentThread (). GetName () + "] consumer wait") LOCK.wait (); System.out.println ("[" + Thread.currentThread (). GetName () + "] consumer wait after");} catch (InterruptedException e) {e.printStackTrace () }} System.out.println ("[" + Thread.currentThread (). GetName () + "] C = >" + num); isProduced = false; LOCK.notifyAll (); System.out.println ("[" + Thread.currentThread (). GetName () + "] notify after") }} 7.2.3, multi-producer, multi-consumer, blocking, wrong version, wasted execution opportunity package chapter2;import java.util.LinkedList;import java.util.stream.Stream;/** * @ author calebzhao * 2019-6-30 7:44 * / public class ProducerAndConsumerVersion3 {private final Object LOCK = new Object (); private boolean isProduced; private int num = 0 Public static void main (String [] args) {Container container = new Container (10); Producer producer = new Producer (container); Consumer consumer = new Consumer (container) Stream.of ("P1", "P2", "P3", "P4"). ForEach ((item)-> {new Thread (()-> {while (true) {producer.produce ();}}, item). Start ();}) Stream.of ("C1", "C2", "C3", "C4") .forEach (item-> {new Thread (()-> {while (true) {consumer.consume ();}}, item). Start (); System.out.println ("main thread execution ends") }} class Producer {private Container container; private int i = 0; public Producer (Container container) {this.container = container;} public void produce () {while (true) {synchronized (container) {if (container.isOverflow ()) {try {container.wait () } catch (InterruptedException e) {e.printStackTrace ();}} else {container.push (+ + I); System.out.println ("[" + Thread.currentThread () .getName () + "] produce" + I) Container.notifyAll ();}} try {Thread.sleep (100);} catch (InterruptedException e) {e.printStackTrace ();} class Consumer {private Container container; public Consumer (Container container) {this.container = container } public void consume () {while (true) {synchronized (container) {if (container.isEmpty ()) {try {container.wait ();} catch (InterruptedException e) {e.printStackTrace () }} / / there is actually a problem here, assuming that both An and B threads execute to container.wait (); in the wait state, the producer executes container.notifyAll (); / / the A thread acquires the object lock from container.wait () Execute later, but because the if..else structure is used here, / / it will cause the A thread to be awakened to acquire the object lock and do nothing, and then release the object lock immediately. Suppose that the A thread runs out of CPU time slices and allows B to acquire the object lock. / / it may follow that B acquires the object lock all the time. While An obviously got the object lock once before but did nothing, it wasted an execution opportunity else {Object value = container.pop () System.out.println ("[" + Thread.currentThread (). GetName () + "] consume" + value); container.notifyAll ();}} class Container {private LinkedList storage; private int capticy; public Container (int capticy) {this.storage = new LinkedList (); this.capticy = capticy } public void push (Object obj) {this.storage.addLast (obj);} public Object pop () {return this.storage.removeFirst ();} public int size () {return this.storage.size ();} public boolean isOverflow () {return size () > = capticy;} public boolean isEmpty () {return this.storage.isEmpty ();}}
7.2.4. Completely correct version of package producer-consumer model; import java.util.LinkedList;import java.util.stream.Stream;/** * @ author calebzhao * 7:44 on 2019-6-30 * / @ SuppressWarnings ("ALL") public class ProducerAndConsumerVersion4 {private final Object LOCK = new Object (); private boolean isProduced; private int num = 0; public static void main (String [] args) {Container2 container = new Container2 (10) Producer2 producer = new Producer2 (container); Consumer2 consumer = new Consumer2 (container); Stream.of ("P1", "P2", "P3", "P4"). ForEach ((item)-> {new Thread ()-> {while (true) {producer.produce ();}}, item). Start () ); Stream.of ("C1", "C2", "C3", "C4") .forEach (item-> {new Thread (()-> {while (true) {consumer.consume ();}}, item). Start ();}) System.out.println ("end of main thread execution");} @ SuppressWarnings ("ALL") class Producer2 {private Container2 container; private int i = 0; public Producer2 (Container2 container) {this.container = container } public void produce () {synchronized (container) {/ / the while here cannot be replaced with if / / assuming that container is now full, C threads consume one by one, and then call container.notifyAll (); notify A, B threads to wake up / / A, B threads from container.wait () When this line of code wakes up and is ready, An acquires the lock and goes to container.wait (); then executes, / / An executes container.push (+ + I); after execution, container is full, An executes the synchronized code block to release the lock, and then B acquires the lock from / / container.wait (); later, if while is replaced with if, B will execute container.push (+ + I) again. / / causes the container to still send data to container push when it is full, so there is an error data while (container.isOverflow ()) {try {container.wait ();} catch (InterruptedException e) {e.printStackTrace () } container.push (+ + I); System.out.println ("[" + Thread.currentThread (). GetName () + "] produce" + I); container.notifyAll ();} try {Thread.sleep (100);} catch (InterruptedException e) {e.printStackTrace () }} @ SuppressWarnings ("ALL") class Consumer2 {private Container2 container; public Consumer2 (Container2 container) {this.container = container;} public void consume () {synchronized (container) {/ / the while here cannot be replaced with if / / assuming that both An and B threads execute to container.wait () At this line of code, An and B are in the wait state, when the producer executes container.notifyAll (); / / then thread An acquires the object lock from container.wait (); then executes back, and container becomes empty after A calls container.pop (). / / if while is replaced with if here, then B acquires the object lock from container.wait (). Execute later, but container is empty / / if you still call container.pop (), you will report ArrayIndeOutOfBoundExeption. While (container.isEmpty ()) {try {container.wait ();} catch (InterruptedException e) {e.printStackTrace () } Object value = container.pop (); System.out.println ("[" + Thread.currentThread (). GetName () + "] consume" + value); container.notifyAll ();}} class Container2 {private LinkedList storage; private int capticy Public Container2 (int capticy) {this.storage = new LinkedList (); this.capticy = capticy;} public void push (Object obj) {this.storage.addLast (obj);} public Object pop () {return this.storage.removeFirst ();} public int size () {return this.storage.size ();} public boolean isOverflow () {return size () > = capticy } public boolean isEmpty () {return this.storage.isEmpty ();}} 8. Catch the exception package chapter2;/** * @ author calebzhao * 22:03 on 2019-7-1 * / public class ExceptionCaught {public static void main (String [] args) {Thread mythread = new Thread (()-> {try {Thread.sleep (1000)) } catch (InterruptedException e) {e.printStackTrace ();}}, "mythread"); / / catch exception mythread.setUncaughtExceptionHandler ((thread, throwable)-> {System.out.println (thread.getName ()); throwable.printStackTrace ();}); mythread.start () Mythread.interrupt ();}}
9. ThreadGrouppackage chapter2;import java.util.stream.Stream;/** * @ author calebzhao * 20:27 on 2019-7-1 * / public class ThreadGroupDemo {public static void main (String [] args) {/ / main method is also a thread, whose name is main System.out.println (Thread.currentThread (). GetName ()) / / the name of the thread group for main threads is main System.out.println (Thread.currentThread (). GetThreadGroup (). GetName ()); / / create thread group tg1 ThreadGroup tg1 = new ThreadGroup ("tg1") New Thread (tg1, "T1") {@ Override public void run () {try {Thread.sleep (1000); / / gets the name of the current thread group System.out.println (this.getThreadGroup () .thread ()) / / get the parent thread group System.out.println (this.getThreadGroup (). GetParent ()) of the current thread group; / / get the name of the parent thread group of the current thread group, System.out.println (this.getThreadGroup (). GetParent (). GetName ()) / / evaluate the number of threads in the parent thread group and children of the current thread group System.out.println (this.getThreadGroup (). GetParent (). ActiveCount ()); System.out.println ("-") } catch (InterruptedException e) {e.printStackTrace ();} .start (); / / create thread group tg2 ThreadGroup tg2 = new ThreadGroup ("tg2"); tg2.setDaemon (true) Stream.of ("T1", "T2", "T3") .forEach (name-> {new Thread (tg2, name) {@ Override public void run () {System.out.println (this.getThreadGroup (). GetName ()); System.out.println (tg1.getParent () System.out.println (this.getThreadGroup (). GetParent (). GetName ()); / evaluate the number of threads under the parent thread group System.out.println (this.getThreadGroup (). GetParent (). ActiveCount ()); Thread [] threads = new Thread [tg1.activeCount ()] This.getThreadGroup () .enumerate (threads); Stream.of (threads) .forEach (System.out::println); System.out.println ("*") / / Test that a thread is the parent of group in the parentOf (group) parameter (directly or indirectly) System.out.println ("parentOf:" + this.getThreadGroup (). GetParent (). ParentOf (this.getThreadGroup ()); System.out.println (Thread.currentThread (). GetName () + "isDaemon:" + this.isDaemon () Try {Thread.sleep (100);} catch (InterruptedException e) {e.printStackTrace ();} .start ();}) / / interrupt all threads under the entire thread group tg2.interrupt ();}}
10. Singleton design pattern version1 (bad) package chapter3.singleton;/** * the problem is that it cannot be loaded lazily. It initializes instance * * @ author calebzhao * 19:47 on 2019-7-4 * / public class SingletonVersion1 {private static final SingletonVersion1 instance = new SingletonVersion1 (); public static SingletonVersion1 getInstance () {return instance;}} version2 (error) package chapter3.singleton / * * @ author calebzhao * 19:47 on 2019-7-4 * / public class SingletonVersion2 {private static SingletonVersion2 instance; private SingletonVersion2 () {} / * multithread concurrent access may enter if code at the same time, resulting in multiple instantiations of * @ return * / public static SingletonVersion2 getInstance () {if (instance = = null) {instance = new SingletonVersion2 ();} return instance }} version3 (bad) package chapter3.singleton;/** * @ author calebzhao * 19:47 on 2019-7-4 * / public class SingletonVersion3 {private static SingletonVersion3 instance Private SingletonVersion3 () {} / * has performance problems. After instantiation, each read must be synchronized with * * @ return * / public static synchronized SingletonVersion3 getInstance () {if (instance = = null) {instance = new SingletonVersion3 ();} return instance;}} version4 (not good)
Jvm instruction reordering may be NullPointerException
Package chapter3.singleton;/** * @ author calebzhao * 19:47 on 2019-7-4 * / public class SingletonVersion4 {private static SingletonVersion4 instance; private static Object LOCK = new Object (); private boolean init; private SomeObject someObject = null; private SingletonVersion4 () {init = true;// try {/ / Thread.sleep (3000); / /} catch (InterruptedException e) {/ / e.printStackTrace () / /} someObject = new SomeObject () } / * there seems to be no problem, but in extreme cases, there will be NPL null pointer exceptions. For example, there are variables in this class that are initialized in the constructor * when multiple threads call getInstance, the instance is instantiated when the first thread accesses null, which allocates memory space in heap memory * after memory space is allocated. However, the constructor is not finished. When the second thread accesses, instance does not return an Instance instance for null, but directly calls the method * new SingletonVersion4 () * * @ return * / public static SingletonVersion4 getInstance () {if (instance = = null) {synchronized (LOCK) {if (instance = = null) {instance = new SingletonVersion4 () } return instance;} public void print () {this.someObject.doSomething ();} public static void main (String [] args) {Thread T1 = new Thread (()-> {SingletonVersion4.getInstance ();}, "T1") Thread T2 = new Thread (()-> {SingletonVersion4.getInstance (). Print ();}, "T2"); t1.start (); t2.start ();}} class SomeObject {public void doSomething () {System.out.println (Thread.currentThread (). GetName () + "doSomething...");}}
Version5 (correct, recommended) package chapter3.singleton;/** * @ author calebzhao * 19:47 on 2019-7-4 * / public class SingletonVersion5 {private SingletonVersion5 () {} private static class Singleton {private static final SingletonVersion5 INSTANCE = new SingletonVersion5 () } / * there seems to be no problem, but in extreme cases, there will be NPL null pointer exceptions. For example, there are variables in this class that are initialized in the constructor * when multiple threads call getInstance, the instance is instantiated when the first thread accesses null, which allocates memory space in heap memory * after memory space is allocated. However, the constructor is not finished. When the second thread accesses, instance does not return an Instance instance for null, but directly calls the method * new SingletonVersion4 () * * @ return * / public static SingletonVersion5 getInstance () {return Singleton.INSTANCE }} version 6 (correct) package chapter3.singleton;/** * @ author calebzhao * 19:47 on 2019-7-4 * / public class SingletonVersion6 {private SingletonVersion6 () {} enum Singleton {INSTANCE; private SingletonVersion6 singletonVersion6 = new SingletonVersion6 (); public SingletonVersion6 getInstance () {return singletonVersion6;}} public static SingletonVersion6 getInstance () {return Singleton.INSTANCE.getInstance () }} 11. Volatile keyword 11.1 three features of high concurrency
Atomicity
For example, I = 9; it may be assigned twice in 16-bit computers, such as low 16-bit assignment success and high 16-bit assignment failure.
In one or more operations, either all succeed or all fail, and there can be no intermediate state.
A = 1; guarantee atomicity
The following steps are performed: 1: read a; 2: add 1 to a; 3: assign the result to a
A = 1: 2 ensures atomicity, and the compilation time will determine the value.
A = axi1 * * non-atomicity * *, step 1: read a; 2: add 1 to a; 3: assign the result to a
Prove that the + + value operation is not atomic.
Package volatileDemo;/** * @ author calebzhao * 22:01 on 2019-7-8 * / public class AtomDemo {private static int initValue = 1; private static int maxValue = 500 / * prove that the + + initValue operation is not atomic, * * assume the following two cases: * 1, if the + + initValue operation is atomic, then the output must not be duplicated * 2, if the + + initValue operation is not familiar with atomicity, but split into 1, read initValue 2 other operations, then it is possible that multiple threads read the same initValue * *, then the following may occur: * T1-> read initValue value is 10 Cpu execution power is switched to T2 * T2-> read initValue value is 10, * T2-> 11 = 10 + 1 * T2-> initValue = 11 * T2-> System.out.printf ("T2 results [% d]\ n", 11) * T1-> 11 = 10 + 1 * T1-> initValue = 11 * T1-> System.out.printf ("result after T1 execution [% d]\ n", 11); * * @ param args * / public static void main (String [] args) {new Thread (()-> {while (initValue)
< maxValue){ System.out.printf("t1 执行后结果 [%d] \n", ++initValue); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() ->{while (initValue
< maxValue){ System.out.printf("t2 执行后结果 [%d] \n", ++initValue); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); }} 可见性 volatile关键字会保证多线程下的内存可见性及指令执行的有序性 https://www.cnblogs.com/yanlong300/p/8986041.html 例子: package volatileDemo;/** * @author calebzhao * 2019/7/8 21:25 */public class VolatileDemo { private static int initValue = 1; private static int MAX_VALUE = 50; public static void main(String[] args) { new Thread(() ->{int localValue = initValue; while (localValue
< MAX_VALUE){// System.out.printf("t1线程读取到initValue的值为 [%d] localValue:[%d]\n", initValue, localValue); if (localValue != initValue){ localValue = initValue; System.out.printf("initValue的值已被更新为 [%d]\n", initValue); }// else{// System.out.printf("t1线程读取到initValue的值为 [%d] localValue:[%d]\n", initValue, localValue);// } } System.out.println("t1线程结束执行"); }, "t1").start(); new Thread(() ->{int localValue = initValue; while (initValue
< MAX_VALUE){ localValue++; initValue = localValue; System.out.printf("更新initValue的值为 [%d]\n", initValue); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2线程结束执行"); }, "t2").start(); }} 当private static volatile int *initValue *= 1; 这句代码加上volatile 关键字就是正确的结果了 有序性 value = 3;void exeToCPUA(){ value = 10; # 可能发生重排序 value的赋值发生在isFinsh之后 isFinsh = true;}void exeToCPUB(){ if(isFinsh){ //value一定等于10?! assert value == 10; }} 试想一下开始执行时,CPU A保存着finished在E(独享)状态,而value并没有保存在它的缓存中。(例如,Invalid)。在这种情况下,value会比finished更迟地抛弃存储缓存。完全有可能CPU B读取finished的值为true,而value的值不等于10。 ** 即isFinsh的赋值在value赋值之前。** 这种在可识别的行为中发生的变化称为重排序(reordings)。注意,这不意味着你的指令的位置被恶意(或者好意)地更改。 它只是意味着其他的CPU会读到跟程序中写入的顺序不一样的结果。 为什么会有指令的重排序? 答案:因为为了使缓存能够得到更加合理地利用。 int a=1int b=2省略一万行代码... int c=a+b 最后一句放第三行就能让缓存更合理, 原因:cpu将a=1读入CPU高速缓存,然后将b=2读入高速缓存,由于CPU高速缓存的容量很小,所以当执行后面的一万行代码时CPU高速缓存满了,那么就会把a=1、b=2这2个缓存行覆盖掉,当真正执行int c= a+ b时由于CPU高速缓存里面没有数据那么CPU就要重新从主存读取数据然后计算,这样就出现了不必须的重复读取主存的操作,浪费CPU,通过重指令排序让int c=a+b放到第三行则可以缓存能够立即得到利用,将c=a+b的结果计算后可以立即回写主内存,避免后续a=1、b=2的缓存行被其他指令的缓存行覆盖 11.2、volatile的内存语义 12、比较并交换(CAS)12.1、使用CAS与使用锁相比的好处 与锁相比,使用比较并交换(CAS)会使程序看起来更复杂,但由于其非阻塞性,它对死锁问题天生免疫,并且线程间的影响也远远比基于锁的方式小的多。更为重要的是,使用无锁的方式完全没有锁竞争带来的开销,也没有线程间频繁调度调来的开销,因此它比基于锁的方式拥有更优越的性能。 12.2、CAS原理 CAS算法的过程是:它包含3个参数CAS(realValue, expectValue, newValue), 其中realValue表示要更新的变量,expectValue表示预期值,newValue表示新值。仅当realValue等与expectValue值时,才将realValue的值变更为newValue,如果realValue与expectValue不相等,说明有其他线程已经更新做了更新,则当前线程什么也不做,最后CAS返回当前realValue的真实值。CAS是抱着乐观的态度去进行的,它总是认为自己可以完成操作,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然允许失败的线程放弃操作。 简单的说,CAS需要你额外给出一个预期值,也就是你认为现在这个变量应该是什么样子的。如果变量不是你想象的那样,则说明已经被别人修改过了。你就重新读取,再次尝试修改就好了。 在硬件层面大部分的处理器都已支持原子化的CAS指令。在JDK5后,虚拟机便可以使用这个指令来进行原子化操作。 13.3、CAS实现计数器package 自旋锁计数;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;public class Counter { /** * 线程安全方式计数, 内部的value子段声明为 private volatile int value;所以保证了内存可见性 */ private AtomicInteger atomic = new AtomicInteger(0); /** * 线程不安全,非原子性操作计数 */ private int i = 0; public void count(){ i++; } public void safeCount(){ while (true){ // 1、多核处理器可能会同时运行到这行代码,单核处理器由于时间片分配算法T1执行到这行代码后CPU执行权被T2获取了, 线程T1、T2均通过get()方法返回0 // 2、假如T1先执行atomic.compareAndSet(currentValue, ++currentValue)这行代码, // 由于currentValue和atomic的值一致,cas操作成功,atomic变成1,退出循环, // 3、然后T2继续执行atomic.compareAndSet(currentValue, ++currentValue); // 这行代码会发现atomic内部维护的value值1已经与currentValue的值0不相等,不会进行设置值操作 // T2继续下次循环, 又执行atomic.get();获取到的currentValue为1, 再次执行compareAndSet时, // atomic为1和currentValue为1相等,成功进行cas操作,然后退出循环 int currentValue = atomic.get(); boolean success = atomic.compareAndSet(currentValue, ++currentValue); if (success){ break; } } } public static void main(String[] args) { Counter counter = new Counter(); List threadList = new ArrayList(500); for (int j = 0; j < 500; j++){ Thread thread = new Thread(() ->{try {Thread.sleep (100);} catch (InterruptedException e) {e.printStackTrace () } counter.count (); counter.safeCount ();}); threadList.add (thread);} threadList.stream () .forEach (thread-> thread.start ()) ThreadList.forEach (thread-> {try {thread.join ();} catch (InterruptedException e) {e.printStackTrace ();}}) System.out.println ("count:" + counter.i); System.out.println ("safeCount:" + counter.atomic);}} 13.3.Use CAS for lock-free synchronization package atomic;import java.util.concurrent.atomic.AtomicInteger;/** * use cas for lock-free synchronization * * @ author calebzhao * 2019-8-23 19:46 * / public class CasLock {private AtomicInteger lock = new AtomicInteger (0) / / record the thread ID private Long getLockThreadId; public void tryLock () {while (true) {boolean success = lock.compareAndSet (0,1); if (success) {getLockThreadId = Thread.currentThread () .getId (); break;} thread {Thread.sleep (1) } catch (InterruptedException e) {e.printStackTrace ();} public void unLock () {long currentThreadId = Thread.currentThread (). GetId (); if (currentThreadId! = getLockThreadId) {throw new IllegalStateException ("lock not acquired, no need to unlock");} int value = lock.get () If (value = = 1) {lock.set (0);}}
Test example:
Package atomic;import java.util.ArrayList;import java.util.BitSet;import java.util.List;import java.util.Random;/** * @ author calebzhao * 19:50 on 2019-8-23 * / public class CasLockDemo {private static int i = 0; public static void main (String [] args) {BitSet bitSet = new BitSet (); CasLock lock = new CasLock (); List threadList = new ArrayList (500); Random random = new Random () For (int j = 0; j)
< 50000; j++){ Thread t = new Thread(() ->{try {Thread.sleep (random.nextInt (20) + 200);} catch (InterruptedException e) {e.printStackTrace ();} lock.tryLock (); iTunes + If (bitSet.get (I)) {throw new RuntimeException ("lock has a problem");} bitSet.set (I); System.out.println (Thread.currentThread (). GetName () + "get lock, I =" + I); lock.unLock ();}, "thread-" + j) ThreadList.add (t);} threadList.forEach (thread-> thread.start ());}} 13, atomic package atomic operation class (unlocked CAS) 13.1, AtomicInteger
Main methods:
Example:
Package atomic;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;/** * @ author calebzhao * 15:07 on 2019-8-25 * / public class AtomicIntegerDemo {/ * Thread-safe count. The internal value subsegment is declared private volatile int value; so memory visibility is guaranteed * / private AtomicInteger atomic = new AtomicInteger (0) / * * unsafe thread, non-atomic operation count * / private int i = 0; public void count () {isafe;} public void safeCount () {atomic.incrementAndGet ();} public static void main (String [] args) {AtomicIntegerDemo counter = new AtomicIntegerDemo (); List threadList = new ArrayList (500); for (int j = 0; j)
< 500; j++){ Thread thread = new Thread(() ->{try {Thread.sleep;} catch (InterruptedException e) {e.printStackTrace ();} counter.count (); counter.safeCount ();}); threadList.add (thread) } threadList.stream () .forEach (thread-> thread.start ()); threadList.forEach (thread-> {try {thread.join ();} catch (InterruptedException e) {e.printStackTrace ();}}); System.out.println ("count:" + counter.i) System.out.println ("safeCount:" + counter.atomic);}}
13.2 、 AtomicReference
AtomicReference, as its name implies, is to update object references atomically.
As you can see, AtomicReference holds a reference to an object, value, and manipulates that reference through the Unsafe class:
Why do I need AtomicReference? Is there a concurrency problem when multiple threads assign values to a reference variable at the same time? There is no concurrency problem with the assignment of the reference variable itself, that is, for the reference variable var, assignment operations such as the following are atomic operations: Foo var =...; AtomicReference is introduced to manipulate shared resources in a manner similar to optimistic locks to improve performance in some scenarios.
We know that when multiple threads access a shared resource at the same time, it is generally necessary to control concurrency in a locked manner:
Volatile Foo sharedValue = value;Lock lock = new ReentrantLock (); lock.lock (); try {/ / operate shared resources sharedValue} finally {lock.unlock ();}
The above access method is actually a pessimistic lock on shared resources. AtomicReference provides the ability to access shared resources in a lock-free manner. Take a look at how to ensure thread safety through AtomicReference. Let's take a look at a specific example:
Package atomic;import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicReference;/** * @ author calebzhao * 20:50 on 2019-8-24 * / public class AtomicReferenceCounter {public static void main (String [] args) throws InterruptedException {AtomicReference ref = new AtomicReference (new Integer (0)); List list = new ArrayList (); for (int I = 0; I
< 1000; i++) { Thread t = new Thread(new Task(ref), "Thread-" + i); list.add(t); } for (Thread t : list) { t.start(); t.join(); } // 打印2000 System.out.println(ref.get()); }}class Task implements Runnable{ private AtomicReference reference; public Task(AtomicReference reference){ this.reference = reference; } @Override public void run() { while (true){ Integer oldValue = reference.get(); boolean success = reference.compareAndSet(oldValue, oldValue + 1); if (success){ break; } } }} 该示例并没有使用锁,而是使用自旋+CAS的无锁操作保证共享变量的线程安全。1000个线程,每个线程对金额增加1,最终结果为2000,如果线程不安全,最终结果应该会小于2000。 通过示例,可以总结出AtomicReference的一般使用模式如下 AtomicReference ref = new AtomicReference(new Object());Object oldCache = ref.get();// 对缓存oldCache做一些操作Object newCache = someFunctionOfOld(oldCache); // 如果期间没有其它线程改变了缓存值,则更新boolean success = ref.compareAndSet(oldCache , newCache); 上面的代码模板就是AtomicReference的常见使用方式,看下compareAndSet方法: 该方法会将入参的expect变量所指向的对象和AtomicReference中的引用对象进行比较,如果两者指向同一个对象,则将AtomicReference中的引用对象重新置为update,修改成功返回true,失败则返回false。也就是说,AtomicReference其实是比较对象的引用。 13.2、CAS操作可能存在的ABA问题13.2.1、介绍 CAS操作可能存在ABA的问题,就是说: 假如一个值原来是A,变成了B,又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了。 一般来讲这并不是什么问题,比如数值运算,线程其实根本不关心变量中途如何变化,只要最终的状态和预期值一样即可。 但是,有些操作会依赖于对象的变化过程,此时的解决思路一般就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A - 2B - 3A。 13.2.3、贵宾充值卡问题(ABA示例) 举例:有一家蛋糕店为了挽留客户,决定为贵宾卡里小于20元的客户一次性充值20元,刺激客户充值和消费,但条件是:每位客户只能被赠送一次。 package atomic;import java.util.concurrent.atomic.AtomicReference;/** * cas ABA问题 * @author calebzhao * 2019/8/25 15:30 */public class CasABAPromblem { private static AtomicReference money = new AtomicReference(19); public static void main(String[] args) { ChargeMoneyWorker chargeMoneyWorker = new ChargeMoneyWorker(money); for (int i = 0; i < 3; i++){ new Thread(chargeMoneyWorker).start(); } ConsumeMoneyWorker consumeMoneyWorker = new ConsumeMoneyWorker(money); new Thread(consumeMoneyWorker).start(); }}class ChargeMoneyWorker implements Runnable{ private AtomicReference money; public ChargeMoneyWorker(AtomicReference money){ this.money = money; } @Override public void run() { while (true){ while (true){ Integer m = money.get(); if (m < 20){ boolean success = money.compareAndSet(m, m +20); if (success){ System.out.println("余额小于20元,充值成功, 充值后余额:" + money.get()); break; } } else {// System.out.println("余额大于20元,无需充值"); break; } } } }}class ConsumeMoneyWorker implements Runnable{ private AtomicReference money; public ConsumeMoneyWorker(AtomicReference money){ this.money = money; } @Override public void run() { while (true){ while (true){ Integer m = money.get(); if (m >10) {boolean success = money.compareAndSet (m, m-10); if (success) {System.out.println ("successful consumption of 10 yuan, balance:" + money.get ()); break }} else {System.out.println ("balance is less than 10 CNY");}} try {Thread.sleep (500);} catch (InterruptedException e) {e.printStackTrace () }
From the above output, the user's account can be recharged repeatedly. The reason is that the user's account balance has been repeatedly modified, resulting in the modified original recharge condition, which makes it impossible for the recharge thread to correctly judge whether the user has been recharged.
Although the probability of this situation is small, it is still possible, so when this problem does occur in the business, we need to pay attention to whether our business itself is unreasonable. JDK takes this situation into account for us, and using AtomicStampedReference can solve this problem very well.
13.3 、 AtomicStampedReference
AtomicStampedReference is the AtomicReference with a version number as mentioned above.
13.3.1, AtomicStampedReference principle
Let's take a look at how to construct an AtomicStampedReference object. AtomicStampedReference has only one constructor:
As you can see, in addition to passing in an initial reference variable initialRef, there is also an initialStamp variable, initialStamp is actually the version number (or timestamp), which is used to uniquely identify the reference variable.
Inside the constructor, a Pair object is instantiated, and the Pair object records the object reference and timestamp information. Int is used as the timestamp. In practice, it is necessary to ensure that the timestamp is unique (usually self-increasing). If the timestamp is repeated, there will be ABA problems.
All the methods of AtomicStampedReference are actually the operations of the Unsafe class on this Pair object. Compared with AtomicReference, every reference variable in AtomicStampedReference is marked with the version number pair.stamp, which solves the ABA problem in CAS.
13.3.2. AtomicStampedReference uses the example / / to create an AtomicStampedReference object and holds a reference to the Foo object, initially null, with the version 0AtomicStampedReference asr = new AtomicStampedReference (null,0); int [] stamp=new int [1]; Foo oldRef = asr.get (stamp); / / call the get method to get the reference object and the corresponding version number
Int oldStamp=stamp [0]; / / stamp [0] saves the version number
Asr.compareAndSet (oldRef, null, oldStamp, oldStamp + 1) / / attempt to update the reference object as CAS and change the version number + 1
The above template is the general use of AtomicStampedReference. Pay attention to the compareAndSet method:
We know that there is a pair object stored inside AtomicStampedReference, and the logic of this method is as follows:
If the reference variable and timestamp of the internal pair of the AtomicStampedReference are the same as the input parameters expectedReference and expectedStamp, it means that no other thread has modified the AtomicStampedReference during the period, and you can modify it. At this point, a new Pair object (the casPair method, because Pair is an Immutable class) is created.
However, there is an optimization logic here, that is, if newReference = = current.reference & & newStamp = = current.stamp, it means that the new value modified by the user is exactly the same as the value currently held in AtomicStampedReference, then there is no need to modify it, just return true directly.
13.3.3. AtomicStampedReference solves the problem of multiple recharge of VIP card package atomic;import java.util.concurrent.atomic.AtomicStampedReference;/** * cas ABA solution * @ author calebzhao * 15:30 on 2019-8-25 * / public class ResolveCasABAProblem {private static AtomicStampedReference money = new AtomicStampedReference (19,0); public static void main (String [] args) {ResolveChargeMoneyWorker chargeMoneyWorker = new ResolveChargeMoneyWorker (money); for (int I = 0; I
< 3; i++){ new Thread(chargeMoneyWorker).start(); } ResolveConsumeMoneyWorker consumeMoneyWorker = new ResolveConsumeMoneyWorker(money); new Thread(consumeMoneyWorker).start(); }}class ResolveChargeMoneyWorker implements Runnable{ private AtomicStampedReference money; public ResolveChargeMoneyWorker(AtomicStampedReference money){ this.money = money; } @Override public void run() { while (true){ try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } while (true){ Integer m = money.getReference(); if (m < 20){ boolean success = money.compareAndSet(m, m +20, 0 , 1); if (success){ System.out.println("余额小于20元,充值成功, 充值后余额:" + money.getReference()); break; } } else {// System.out.println("余额大于20元,无需充值"); break; } } } }}class ResolveConsumeMoneyWorker implements Runnable{ private AtomicStampedReference money; public ResolveConsumeMoneyWorker(AtomicStampedReference money){ this.money = money; } @Override public void run() { while (true){ while (true){ Integer m = money.getReference(); int stamp = money.getStamp(); if (m >10) {/ / Why not add 1 to the version number here? / / if this becomes boolean success = money.compareAndSet (m, m-10, stamp, stamp + 1) / / consider a situation where the user account itself has 19 yuan, and the initial top-up status is 0, which means top-up. The user first consumes 10 yuan, and the stamp becomes 1 / 1. At this time, the user has not yet recharged, and the account amount is indeed less than 20 yuan. This will cause the top-up thread to scan and find that the stamp has changed to 1. The fundamental reason is that whether the user consumes or not has nothing to do with whether the user has been recharged, and the state of the recharge cannot be changed due to other factors / / this example is an example in the book "practical High concurrent programming". The examples in the book do not take this into account. / / the hypothetical situation in the book is to recharge first and then consume later. But if it is consumption first and then recharge, there will be a problem. Boolean success = money.compareAndSet (m, m-10, stamp, stamp) If (success) {System.out.println ("successful consumption of 10 yuan, balance: + money.getReference ()); break;}} else {System.out.println (" balance less than 10 yuan ") }} try {Thread.sleep;} catch (InterruptedException e) {e.printStackTrace ();}
Case 1: recharge before consumption
Case 2: consume first and recharge later
13.4 、 AtomicMarkableReference
AtomicMarkableReference is a specialized form of AtomicStampedReference AtomicMarkableReference is used to not know which version of the data is currently, just to know if the data has been changed.
In the previous customer account recharge example, the code for solving the ABA problem using AtomicMarkableReference is as follows:
Package atomic;/** * @ author calebzhao * 17:45 on 2019-8-25 * / import java.util.concurrent.atomic.AtomicMarkableReference;/** * cas ABA questions * @ author calebzhao * 2019-8-25 15:30 * / public class AtomicMarkableReferenceDemo {private static AtomicMarkableReference money = new AtomicMarkableReference (19, false); public static void main (String [] args) {MarkableChargeMoneyWorker chargeMoneyWorker = new MarkableChargeMoneyWorker (money); for (int I = 0; I
< 3; i++){ new Thread(chargeMoneyWorker).start(); } MarkableConsumeMoneyWorker consumeMoneyWorker = new MarkableConsumeMoneyWorker(money); new Thread(consumeMoneyWorker).start(); }}class MarkableChargeMoneyWorker implements Runnable{ private AtomicMarkableReference money; public MarkableChargeMoneyWorker(AtomicMarkableReference money){ this.money = money; } @Override public void run() { while (true){ try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } while (true){ Integer m = money.getReference(); if (m < 20){ boolean success = money.compareAndSet(m, m +20, false , true); if (success){ System.out.println("余额小于20元,充值成功, 充值后余额:" + money.getReference()); break; } } else {// System.out.println("余额大于20元,无需充值"); break; } } } }}class MarkableConsumeMoneyWorker implements Runnable{ private AtomicMarkableReference money; public MarkableConsumeMoneyWorker(AtomicMarkableReference money){ this.money = money; } @Override public void run() { while (true){ while (true){ Integer m = money.getReference(); boolean isMarked = money.isMarked(); if (m >10) {/ / consumption does not change the recharge logo boolean success = money.compareAndSet (m, m-10, isMarked, isMarked); if (success) {System.out.println ("successful consumption of 10 yuan, balance:" + money.getReference ()); break }} else {System.out.println ("balance is less than 10 CNY");}} try {Thread.sleep (500);} catch (InterruptedException e) {e.printStackTrace () }
13.4 、 AtomicIntegerArray
AtomicIntegerArray is essentially an encapsulation of the int [] type, which uses the Unsafe class to control the security of int [] under multithreading through CAS. It provides the following core API:
13.4.1, if there is no error example of AtomicIntegerArray package atomic / * Increment tasks: this class uses the vector [I] + method to increase the values of all elements in the array * Decrement tasks: this class uses the vector [I]-method to reduce the values of all elements in the array * * created an int [] array of 1000 elements in the main method, * executed 1000 Increment tasks and 1000 Decrement tasks, if there are no inconsistent errors at the end of the task * the values of all elements in the array are not all 0. After executing the program, you will see that the program outputs some values that are not all 0 * @ author calebzhao * 12:22 on 2019-8-25 * / public class BadAtomiceIntegerArrayDemo {public static void main (String [] args) throws InterruptedException {int [] vector = new int [1000] For (int I = 0; I
< vector.length; i++){ vector[i] = 0; } BadIncrement increment = new BadIncrement(vector); BadDecrement decrement = new BadDecrement(vector); Thread[] badThreadIncrements = new Thread[1000]; Thread[] badThreadDecrements = new Thread[1000]; for (int i =0 ; i < badThreadIncrements.length; i++){ badThreadIncrements[i] = new Thread(increment); badThreadDecrements[i] = new Thread(decrement); } for (int i =0 ; i < badThreadIncrements.length; i++){ badThreadIncrements[i].start(); badThreadDecrements[i].start(); } for (int i =0 ; i < badThreadIncrements.length; i++){ badThreadIncrements[i].join(); badThreadDecrements[i].join(); } for (int i =0 ; i < vector.length; i++){ if (vector[i] != 0){ System.out.println("Vector["+i+"] : " + vector[i]); } } System.out.println("main end"); }}class BadIncrement implements Runnable{ private int[] vector; public BadIncrement(int[] vector){ this.vector = vector; } @Override public void run() { for (int i = 0; i thread.start()); threadList.forEach(thread ->{try {thread.join ();} catch (InterruptedException e) {e.printStackTrace ();}}); long time = System.currentTimeMillis ()-start; System.out.println ("casCounter:" + casCounter.getValue () + "time:" + time) } catch (Error e) {e.printStackTrace ();}} public void synchronizedCouterTest () {try {SynchronizedCouter synchronizedCouter = new SynchronizedCouter (); CouterThread couterThread = new CouterThread (synchronizedCouter); List threadList = new ArrayList (); for (int j = 0; j)
< THREAD_COUNT; j++){ Thread thread = new Thread(couterThread); threadList.add(thread); } long start = System.currentTimeMillis(); threadList.stream().forEach(thread ->Thread.start (); threadList.forEach (thread-> {try {thread.join ();} catch (InterruptedException e) {e.printStackTrace ();}}); long time = System.currentTimeMillis ()-start System.out.println ("synchronizedCounter:" + synchronizedCouter.getValue () + "time:" + time);} catch (Exception e) {e.printStackTrace ();}} public void badCouterTest () {try {BadCouter badCouter = new BadCouter (); CouterThread couterThread = new CouterThread (badCouter); List threadList = new ArrayList () For (int j = 0; j
< THREAD_COUNT; j++){ Thread thread = new Thread(couterThread); threadList.add(thread); } long start = System.currentTimeMillis(); threadList.stream().forEach(thread ->Thread.start (); threadList.forEach (thread-> {try {thread.join ();} catch (InterruptedException e) {e.printStackTrace ();}}); long time = System.currentTimeMillis ()-start System.out.println ("badCouter:" + badCouter.getValue () + "time:" + time);} catch (Exception e) {e.printStackTrace ();} class CouterThread implements Runnable {private Counter counter; public CouterThread (Counter counter) {this.counter = counter } @ Override public void run () {try {Thread.sleep (100);} catch (InterruptedException e) {e.printStackTrace ();} counter.incrementAndGet ();}} interface Counter {int incrementAndGet (); int getAndIncrement (); int getValue () } class AtomicIntegerCouter implements Counter {/ * Thread-safe count, internal value subsegments are declared private volatile int value; so memory visibility is guaranteed * / private AtomicInteger atomic = new AtomicInteger (0) @ Override public int incrementAndGet () {while (true) {/ / 1, the multicore processor may run this line of code at the same time. The CPU execution power of the single-core processor is acquired by T2 after the time slice allocation algorithm T1 executes this line of code. Threads T1 and T2 all return 0 / / 2 through the get () method. If T1 executes atomic.compareAndSet (currentValue, + + currentValue) first. / / because the values of currentValue and atomic are the same, the cas operation is successful, atomic becomes 1, exit the loop, / / 3, and then T2 continues to execute atomic.compareAndSet (currentValue, + + currentValue) / / this line of code will find that the value value 1 maintained internally by atomic is not equal to the currentValue value 0, and will not set the value operation / / T2 to continue the next loop and execute atomic.get () The obtained currentValue is 1. When compareAndSet is executed again, / / atomic is 1 and currentValue is 1. Successfully perform the cas operation, and then exit the loop int currentValue = atomic.get (); boolean success = atomic.compareAndSet (currentValue, + + currentValue); if (success) {return atomic.get () }} @ Override public int getAndIncrement () {while (true) {int currentValue = atomic.get (); boolean success = atomic.compareAndSet (currentValue, + + currentValue); if (success) {return currentValue;}} @ Override public int getValue () {return atomic.get () } class CasCounter implements Counter {private volatile int count = 0; private static final Unsafe unsafe = UnsafeUtil.getUnsafe (); private static long valueOffset; static {try {valueOffset = unsafe.objectFieldOffset (CasCounter.class.getDeclaredField ("count"));} catch (NoSuchFieldException e) {throw new Error () } @ Override public int incrementAndGet () {while (true) {boolean success = unsafe.compareAndSwapInt (this, valueOffset, count, count + 1); if (success) {return count }} @ Override public int getAndIncrement () {while (true) {boolean success = unsafe.compareAndSwapInt (this, valueOffset, count, count + 1); if (success) {return count;}} @ Override public int getValue () {return count }} class BadCouter implements Counter {private volatile int count = 0; @ Override public int incrementAndGet () {return + + count;} @ Override public int getAndIncrement () {return count++;} @ Override public int getValue () {return count;}} class SynchronizedCouter implements Counter {private volatile int count = 0; @ Override public synchronized int incrementAndGet () {return + + count } @ Override public synchronized int getAndIncrement () {return count++;} @ Override public int getValue () {return count;}}
If you run the sample code many times, you will find that basically each synchronizedCouter takes the least time, which also shows that the performance of the way synchronized uses locks is not necessarily low.
The above is the introduction process of java concurrent programming, and the editor believes that there are some knowledge points that we may see or use in our daily work. I hope you can learn more from this article. For more details, please follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.