1What is a concurrency problem.
Multiple processes or threads accessing the same resource simultaneously (or in the same period of time) will cause concurrency problems.
A typical example is that two bank operators operate the same account at the same time. For example, operators A and B read an account with a balance of 1,000 yuan at the same time, operator A adds 100 yuan to the account, operator B also reduces 50 yuan for the account, A submits first, and B submits later. The final actual account balance is 1000-50=950 yuan, but it should have been 1000+100-50=1050. This is a typical concurrency problem. How to solve it? Can use locks.
2Usage of synchronized in java
Usage 1
public class Test{public synchronized void print(){….;}}If a thread executes the print() method, the object will be locked. Other threads will not be able to execute all synchronized blocks of the object.
Usage 2
public class Test{public void print(){synchronized(this){//Lock this object…;}}} Same usage 1, but it can better reflect the essence of synchronized usage.
Usage 3
public class Test{private String a = "test";public void print(){synchronized(a){//Lock a object...;}}public synchronized void t(){…;//This synchronized code block will not be locked because of print().}}Execution of print() will lock object a. Note that it is not locking the object of Test, which means that other synchronized methods of the Test object will not be locked because of print(). After the synchronization code block is executed, the lock to a is released.
In order to lock the code block of an object without affecting the high-performance writing of other synchronized blocks of the object:
public class Test{private byte[] lock = new byte[0];public void print(){synchronized(lock){…;}}public synchronized void t(){…;}}Static method lock
public class Test{public synchronized static void execute(){…;}}The same effect
public class Test{public static void execute(){synchronized(TestThread.class){…;}}}3 Locks in Java and queue up to go to the toilet.
Lock is a way to prevent other processes or threads from accessing resources, that is, the locked resource cannot be accessed by other requests. In JAVA, the sychronized keyword is used to lock an object. for example:
public class MyStack {int idx = 0;char [] data = new char[6];public synchronized void push(char c) {data[idx] = c;idx++;}public synchronized char pop() {idx--;return data[idx];}public static void main(String args[]){MyStack m = new MyStack();/** The following object m is locked. Strictly speaking, all synchronized blocks of object m are locked. If there is another thread T trying to access m, then T cannot execute the push and pop methods of the m object. */m.pop();//Object m is locked. }}Java's lock-up unlocking is exactly the same as multiple people queuing up for a public toilet. The first person locked the door from inside after entering, so the others had to wait in line. The door will only open (unlock) when the first person comes out after the end. It was the second person's turn to go in, and he would lock the door from inside again, and the others would continue to wait in line.
It is easy to understand using the toilet theory: when a person enters a toilet, the toilet will be locked, but it will not cause the other toilet to be locked, because one person cannot squat in two toilets at the same time. For Java, it means: the locks in Java are targeted at the same object, not at the class. See the following example:
MyStatckm1=newMyStack();MyStatckm2=newMystatck();m1.pop();m2.pop();
The locks of m1 objects will not affect the locks of m2 because they are not the same toilet position. That is to say, if there are 3 threads t1, t2, and t3 operating m1, then these 3 threads can only queue and wait on m1. Assuming that the other 2 threads t8, t9 operating m2, then t8, t9 will only wait on m2. t2 and t8 have nothing to do with it. Even if the lock on m2 is released, t1, t2, and t3 may still have to queue on m1. There is no reason, it is not the same toilet seat.
Java cannot add two locks to a code block at the same time. This is different from the database lock mechanism. The database can add several different locks to a record at the same time.
4When should I release the lock?
Generally, the lock is released after the synchronization code block (locked code block) is executed, or the lock can be released halfway through wait() method. The wait() method is like squatting in the toilet halfway and suddenly I found that the sewer was blocked. I had to come out and stand aside so that the sewer repairer (a thread that is ready to execute notify) can go in and clear the toilet. After the unblocking, the master shouted: "It has been repaired." The comrade who came out just now lined up again after hearing it. Pay attention, you must wait for the master to come out. If the master doesn’t come out, no one can get in. That is to say, after notify, other threads can enter the blocked area and move around immediately, but other threads can enter after the blocked area where the notify code is executed and released to release the lock.
Here is the wait and notify code example:
public synchronized char pop() {char c;while (buffer.size() == 0) {try {this.wait();//Out of the toilet}catch (InterruptedException e) {// ignore it…}}c = ((Character)buffer.remove(buffer.size()-1)). charValue();return c;}public synchronized void push(char c) {this.notify();//Notify those wait() threads to queue up again. Note: Just notify them to re-queule. Character charObj = new Character(c);buffer.addElement(charObj);}//Execute and release the lock. Those queued threads can come in.Go deeper.
Due to the wait() operation, the comrades who came out halfway would not queue up until they received the notify signal. He would watch the people queuing next to him (the water pipe repair master is also among them). Note that the water pipe repair master cannot go to the line, and he must queue up like those who go to the toilet. It is not that after a person squats out halfway, the water pipe repair master can suddenly appear and go in to repair it immediately. He wants to compete fairly with the people who were originally queuing, because he is also an ordinary thread. If the water pipe repair master is lined behind, the person in front will find that it is blocked and wait, then come out and stand aside, wait, come out, stand aside, and only go to the master to go in and execute notify. In this way, after a while, there will be a bunch of people standing next to the queue, waiting for notify.
Finally, the master went in and notify. What's next?
1. A wait person (thread) is notified.
2. Why is it him notified instead of another wait person? Depends on the JVM. We can't pre-emptive
Determine which one will be notified. In other words, those with high priority may not be awakened first, waiting
Any time is not necessarily awakened first, everything is unpredictable! (Of course, if you know the JVM
If implemented, you can predict it).
3. He (the thread notified) needs to queue up again.
4. Will he be in the first place in the line? The answer is: Not sure. Will he be the last one? Not necessarily.
But if the thread's priority is set to be relatively high, then the probability of it being ranked first is relatively high.
5. When it is his turn to re-enter the toilet seat, he will continue to execute from the last wait() place, and will not re-execute.
To put it in a disgusting way, he will continue to ramble, not to ramble again.
6. If the master notifyAll(), then all the people who gave up halfway will line up again. The order is unknown.
JavaDOC says that Theawakenedthreadswillnotbeabletoproceeduntilthecurrentthreadrelinquishthelockonthisobject (the awakened thread cannot be executed before the current thread releases the lock).
This is obvious to explain using toilet seat theory.
5Lock usage
Use the synchronized keyword to lock resources. The Lock keyword is also OK. It is new in JDK1.5. The usage is as follows:
class BoundedBuffer {final Lock lock = new ReentrantLock();final Condition notFull = lock.newCondition();final Condition notEmpty = lock.newCondition();final Object[] items = new Object[100];int putptr, takeptr, count;public void put(Object x) throws InterruptedException {lock.lock();try {while (count == items.length) notFull.await();items[putptr] = x;if (++putptr == items.length) putptr = 0;++count;notEmpty.signal();} finally {lock.unlock();}}public Object take() throws InterruptedException {lock.lock();try {while (count == 0) notEmpty.await();Object x = items[takeptr];if (++takeptr == items.length) takeptr = 0;--count;notFull.signal();return x;} finally {lock.unlock();}}}(Note: This is an example in JavaDoc, an implementation example of a blocking queue. The so-called blocking queue means that if a queue is full or empty, it will cause thread blocking and waiting. The ArrayBlockingQueue in Java provides a ready-made blocking queue, and you don't need to write one of them specifically.)
The code between lock.lock() and lock.unlock() of an object will be locked. What is the better thing about this method compared to synchronize? In short, it classifies wait threads. To describe it using the toilet seat theory, those who squat halfway and come out of the toilet seat and wait may have different reasons. Some are because the toilet is blocked, and some are because the toilet is out of water. When notify, you can shout: If you wait for the toilet to be blocked, you will be back in line (for example, the problem of the toilet blocked has been solved), or shout, if you wait for the toilet to be blocked, you will be back in line (for example, the problem of the toilet to be blocked has been solved). This allows for more detailed control. Unlike the wait and notify in synchronize, whether the toilet is blocked or the toilet is not watery, you can only shout: I just waited here to queue up! If the people in line come in and look, they find that the problem of the toilet is just solved, and the problem that they are eager to solve (the toilet has no water) has not been solved yet, so they have to go back and wait (wait), and come in for a walk in in vain, wasting time and resources.
The corresponding relationship between Lock method and synchronized:
LockawaitsignalsignalAll
synchronizedwaitnotifynotifyAll
Note: Do not call wait, notify, notifyAll in blocks locked by Lock
6. Use pipelines to communicate between threads
The principle is simple. Two threads, one operates PipedInputStream and the other operates PipedOutputStream. The data written by PipedOutputStream is cached in the Buffer first. If the Buffer is full, this thread waits. PipedInputStream reads out the data in the Buffer. If the Buffer has no data, this thread waits.
The same function can be achieved by blocking queues in jdk1.5.
package io;import java.io.*;public class PipedStreamTest {public static void main(String[] args) {PipedOutputStream ops=new PipedOutputStream();PipedInputStream pis=new PipedInputStream();try{ops.connect(pis);//Implement pipeline connection new Producer(ops).run();new Consumer(pis).run();}catch(Exception e){e.printStackTrace();}}}//Producer class Producer implements Runnable{private PipedOutputStream ops;public Producer(PipedOutputStream ops) {this.ops=ops;}public void run(){try{ops.write("hell,spell".getBytes());ops.close();}catch(Exception e) {e.printStackTrace();}}}//Consumer class Consumer implements Runnable{private PipedInputStream pis;public Consumer(PipedInputStream pis) {this.pis=pis;}public void run(){try{byte[] bu=new byte[100];int len=pis.read(bu);System.out.println(new String(bu,0,len));pis.close();}catch(Exception e) {e.printStackTrace();}}}Example 2: A little change to the above program becomes two threads.
package io;import java.io.*;public class PipedStreamTest {public static void main(String[] args) {PipedOutputStream ops=new PipedOutputStream();PipedInputStream pis=new PipedInputStream();try{ops.connect(pis);//Implement pipeline connection Producer p = new Producer(ops);new Thread(p).start();Consumer c = new Consumer(pis);new Thread(c).start();}catch(Exception e){e.printStackTrace();}}}}//Producer class Producer implements Runnable{private PipedOutputStream ops;public Producer(PipedOutputStream ops) {this.ops=ops;}public void run(){try{for (;;){ops.write("hell,spell".getBytes());ops.close();}}}//Consumer class Consumer implements Runnable{private PipedInputStream pis;public Consumer(PipedInputStream pis) {this.pis=pis;}public void run(){try{for (;;){byte[] bu=new byte[100];int len=pis.read(bu);System.out.println(new String(bu,0,len));}pis.close();}catch(Exception e) {e.printStackTrace();}}}Example 3. This example is more appropriate to the application
import java.io.*;public class PipedIO {//After the program is running, copy the content of the sendFile file to the receiverFile file public static void main(String args[]){try{//Construct the read and write pipeline flow object PipedInputStream pis=new PipedInputStream();PipedOutputStream pos=new PipedOutputStream();//Implement the association pos.connect(pis);//Construct two threads and start. new Sender(pos,"c:/text2.txt").start();new Receiver(pis,"c:/text3.txt").start();}catch(IOException e){System.out.println("Pipe Error" + e);}}}}//Thread sends class Sender extends Thread{PipedOutputStream pos;File file;//Constructor method Sender(PipedOutputStream pos, String fileName){this.pos=pos;file=new File(fileName);}//Thread run method public void run(){try{//Read file content FileInputStream fs=new FileInputStream(file);int data;while((data=fs.read())!=-1){//Write to the start of the pipeline pos.write(data);}pos.close();}catch(IOException e) {System.out.println("Sender Error" +e);}}}}//Thread class Receiver extends Thread{PipedInputStream pis;File file;//Construction method Receiver(PipedInputStream pis, String fileName){this.pis=pis;file=new File(fileName);}//Thread runs public void run(){try {//Write file stream object FileOutputStream fs=new FileOutputStream(file);int data;//Read while((data=pis.read())!=-1){//Write to local file fs.write(data);}pis.close();}catch(IOException e){System.out.println("Receiver Error" +e);}}}7 Blocking queue
The blocking queue can replace the pipeline flow method to implement the inlet/drainage pipe mode (producer/consumer). JDK1.5 provides several ready-made blocking queues. Now let's look at the code of ArrayBlockingQueue as follows:
Here is a blocking queue
BlockingQueue blockingQ = new ArrayBlockingQueue 10;
A thread takes from the queue
for(;;){ Object o = blockingQ.take();//The queue is empty, then wait (blocking) }Another thread is stored in the queue
for(;;){ blockingQ.put(new Object());//If the queue is full, wait (blocking) }It can be seen that blocking queues are simpler to use than pipelines.
8Use Executors, Executor, ExecutorService, ThreadPoolExecutor
You can use thread management tasks. You can also use a set of classes provided by jdk1.5 to more conveniently manage tasks. From these categories we can experience a task-oriented way of thinking. These classes are:
Executor interface. How to use:
Executor executor = anExecutor;// Generate an Executor instance. executor.execute(new RunnableTask1());
Intention: Users only focus on task execution, and do not have to worry about task creation and execution details, and other issues that third-party implementers are concerned about. That is to say, decouple the task call execution and the task implementation.
In fact, there are already excellent implementations of this interface in JDK1.5. Enough.
Executors is a factory class or tool class like Collections, used to generate instances of various interfaces.
The ExecutorService interface is inherited from Executor.Executor just throws the task into executor() for execution, and ignores the rest. ExecutorService is different, it will do more control work. for example:
class NetworkService {private final ServerSocket serverSocket;private final ExecutorService pool;public NetworkService(int port, int poolSize) throws IOException {serverSocket = new ServerSocket(port);pool = Executors.newFixedThreadPool(poolSize);}public void serve() {try {for (;;) {pool.execute(new Handler(serverSocket.accept()));}}catch (IOException ex) {pool.shutdown();//No new tasks are executed}}}class Handler implements Runnable {private final Socket socket;Handler(Socket socket) {this.socket = socket;}public void run() {// read and service request}}After ExecutorService (that is, the pool object in the code) executes shutdown, it can no longer execute new tasks, but the old tasks will continue to be executed, and those waiting for execution will no longer wait.
Task Submitter and Performer Communication
public static void main(String args[])throws Exception {ExecutorService executor = Executors.newSingleThreadExecutor();Callable task = new Callable(){public String call()throws Exception{return "test";}};Future f = executor.submit(task);String result = f.get();//Waiting (blocking) return result System.out.println(result);executor.shutdown();}Executor instance obtained by Executors.newSingleThreadExecutor() has the following characteristics:
Task execution sequentially. For example:
executor.submit(task1); executor.submit(task2);
You must wait for task1 to be executed before task2 can be executed.
task1 and task2 will be placed in a queue and processed by a worker thread. That is: there are 2 threads in total (main thread, worker thread that processes tasks).
For other classes, please refer to JavaDoc
9 Concurrent Process Control
The examples in this section are from Wen Shao's Java concurrency tutorial and may be changed. Salute to Mr. Wen.
CountDownLatch Door Pin Counter
Start the thread and wait for the thread to end. That is, the common main thread and other child threads are executed after the end of the main thread.
public static void main(String[] args)throws Exception {// TODO Auto-generated method stub final int count=10;final CountDownLatch completeLatch = new CountDownLatch(count);//Define the number of door latches is 10 for (int i=0;i<count;i++){Thread thread = new Thread("worker thread"+i){public void run(){//do xxxx completeLatch.countDown();//Reduce one door latch}};thread.start();}completeLatch.await();//If the door latch has not been reduced yet, wait. }In JDK1.4, the common method is to set the status of the child thread and loop detection of the main thread. Ease of use and efficiency are not good.
Start many threads and wait for notifications to start
public static void main(String[] args) throws Exception {// TODO Auto-generated method stub final CountDownLatch startLatch = new CountDownLatch(1);//Define a door latch for (int i = 0; i < 10; i++) {Thread thread = new Thread("worker thread" + i) {public void run() {try {startLatch.await();//If the door latch has not been reduced yet, wait}catch (InterruptedException e) {}// do xxxx}};thread.start();}startLatch.countDown();//Reduce one door latch}CycliBarrier. Only after all threads reach a starting line can they continue running.
public class CycliBarrierTest implements Runnable {private CycliBarrier barrier;public CycliBarrierTest(CycliBarrier barrier) {this.barrier = barrier;}public void run() {//do xxxx;try {this.barrier.await();//The thread will check if all other threads are arrived. If you don't arrive, continue to wait. When all is reached, execute the contents of the barrier's run function body}catch (Exception e) {}}/** * @param args */public static void main(String[] args) {//parameter 2 means that both threads have reached the starting line before they start to execute together CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {public void run() {//do xxxx;}});Thread t1 = new Thread(new CycliBarrierTest(barrier));Thread t2 = new Thread(new CycliBarrierTest(barrier));t1.start();t2.start();}}This simplifies the traditional way to implement this function with counter + wait/notifyAll.
10 Concurrency 3 Law
Amdahl's Law. Given the problem scale, the parallelization part accounts for 12%. Then, even if parallelism is applied to the extreme, the performance of the system can only be improved by 1/(1-0.12)=1.136 times at most. That is: parallelism has an upper limit to improving system performance.
Gustafson's Law. Gustafson's Law says Amdahl's Law does not consider that more computing power can be used as the number of CPUs increases. The essence is to change the scale of the problem so that the remaining 88% of the serial processing in Amdahl's law can be parallelized, thus breaking through the performance threshold. In essence, it is a kind of space exchange time.
Sun-Ni law. It is a further promotion of the first two laws. The main idea is that the speed of computing is limited by the speed of storage rather than CPU. Therefore, we must make full use of computing resources such as storage space and try to increase the scale of the problem to produce better/more accurate solutions.
11 From concurrent to parallel
Computers need to calculate quickly when identifying objects, so that the chips get hot and hot. When people recognize objects, they are clear at a glance, but they do not cause a certain brain cell to be burned (exaggerated) and feel uncomfortable. This is because the brain is a distributed parallel operation system. Just like Google can use some cheap Linux servers to perform huge and complex calculations, countless neurons in the brain calculate independently, sharing the results with each other, and instantly complete the effect that requires trillions of operations for a single CPU. Just imagine, if it is created in the field of parallel processing, it will have an immeasurable impact on the development and future of computers. Of course, the challenges can also be imagined: many problems are not easily "divided".
Summarize
The above is the entire content of this article about the Java concurrency problem, and I hope it will be helpful to everyone. Interested friends can continue to refer to other related topics on this site. If there are any shortcomings, please leave a message to point it out. Thank you friends for your support for this site!