First, the synchronization problem is raised
Thread synchronization is designed to prevent multiple threads from accessing a data object from corrupting the data.
For example, two threads, ThreadA and ThreadB, operate on the same object, Foo, and modify data on Foo.
public class Foo { private int x = 100; public int getX() { return x; } public int fix(int y) { x = x - y; return x; }}Copy the code
public class MyRunnable implements Runnable { private Foo foo = new Foo(); public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread ta = new Thread(r, "Thread-A"); Thread tb = new Thread(r, "Thread-B"); ta.start(); tb.start(); } public void run() { for (int i = 0; i < 3; i++) { this.fix(30); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println (thread.currentThread ().getName() + ": x value of current foo =" + foo.getx ()); } } public int fix(int y) { return foo.fix(y); }}Copy the code
Running results:
Thread-a: the current x value of foo is 40 Thread-b: the current x value of foo is 40 Thread-b: the current x value of foo is -20 Thread-a: the current x value of foo is -50 thread-a: The current x value of foo is -50. The x value of the current foo object = -80 Thread-b: the x value of the current foo object = -80 Process Finished with exit code 0Copy the code
The result shows that the output value is obviously unreasonable. The reason is that two threads are accessing Foo uncontrollably and modifying its data.
To keep the result reasonable, only one goal would be to restrict access to Foo to one thread at a time. This ensures that the data in Foo is valid.
In concrete Java code, you need to do two things:
Mark the variable x of the competing resource class Foo as private;
Synchronize code that changes variables, using the synchronized keyword to synchronize methods or code.
Second, synchronization and locking
1. The principle of lock
Every object in Java has a built-in lock
When a program runs on a non-static synchronized method, the lock associated with the current instance (this instance) of the executing code class is automatically acquired. Acquiring a lock on an object is also called acquiring a lock, locking an object, locking on an object, or synchronizing on an object.
The object lock is only active when the program runs into a synchronized method or block of code.
An object has only one lock. So, if one thread acquires the lock, no other thread can acquire it until the first thread releases (or returns) the lock. This also means that no other thread can enter a synchronized method or code block on the object until the lock is released.
A lock release is when the thread holding the lock exits a synchronized method or block of code.
There are a few key points about locking and synchronization:
1) Only methods can be synchronized, not variables and classes;
2) Only one lock per object; When it comes to synchronization, it should be clear on what? That is, what object is synchronized on?
3) Instead of synchronizing all methods in a class, a class can have both synchronized and unsynchronized methods.
4) If two threads execute a synchronized method in a class, and both threads invoke the method using the same instance, only one thread can execute the method at a time, and the other must wait until the lock is released. That is, if one thread acquires a lock on an object, no other thread can access any of the synchronized methods in the class.
5) If the thread has both synchronous and asynchronous methods, the asynchronous method can be freely accessed by multiple threads without being restricted by locks.
6) When a thread is sleeping, any locks it holds are not released.
7) Threads can acquire multiple locks. For example, calling the synchronization method of one object from within the synchronization method of another object acquires the synchronization lock of both objects.
8) Synchronization damages concurrency and should be minimized as far as possible. Synchronization can synchronize not only the entire method, but also some code blocks within the method.
9) When using a synchronization block, you should specify which object to synchronize on, that is, which object to acquire the lock. Such as:
public int fix(int y) {
synchronized (this) {
x = x - y;
}
return x;
}Copy the code
Of course, synchronous methods can also be rewritten to asynchronous methods, but the function is exactly the same, for example:
public synchronized int getX() {
return x++;
}Copy the code
with
public int getX() { synchronized (this) { return x; }}Copy the code
The effect is exactly the same.
Static method synchronization
To synchronize static methods, a lock is required for the entire class object, which is this class (xxx.class).
Such as:
public static synchronized int setName(String name){
Xxx.name = name;
}Copy the code
Is equivalent to
public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; }}Copy the code
What happens if the thread cannot acquire the lock
If a thread tries to enter a synchronous method and its lock is already occupied, the thread is blocked on that object. Essentially, a thread enters a kind of pool of the object and must wait there until its lock is released and the thread becomes runnable or runnable again.
When considering blocking, it is important to note which object is being used for locking:
1. Threads calling non-statically synchronized methods on the same object block each other. In the case of different objects, each thread has its own lock on the object and does not interfere with each other.
2. Threads calling statically synchronized methods in the same Class block each other and are locked to the same Class object.
3. Statically synchronized methods and non-statically synchronized methods will never block each other, because static methods are locked to Class objects and non-static methods are locked to objects of that Class.
4. For synchronized blocks, see what objects are already used for locking (parentheses after synchronized). Threads synchronized on the same object will block each other, and threads locked on different objects will never block each other.
5. When to synchronize
When mutually exclusive (exchangeable) data is accessed by multiple threads at the same time, synchronization should be performed to protect the data and ensure that both threads do not modify it at the same time.
For modifiable data in non-static fields, non-static methods are typically used to access it.
For mutable data in static fields, static methods are typically used to access it.
If you need to use a static field in a non-static method, or call a non-static method in a static field, the problem becomes very complicated. It’s beyond SJCP.
Thread-safe classes
A class is said to be “thread-safe” when it has been well synchronized to protect its data.
Even thread-safe classes should be especially careful because the threads operating on them are still not necessarily safe.
For example, if a collection is thread-safe, two threads working on the same collection object will delete all elements of the collection when the first thread queries the collection for non-empty. The second thread also performs the same operation as the first thread. Maybe after the first thread queries, the second thread also queries that the collection is not empty, but after the first thread performs the purge, the second thread does the delete, because the collection is already empty.
Here’s a code:
public class NameList { private List nameList = Collections.synchronizedList(new LinkedList()); public void add(String name) { nameList.add(name); } public String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { return null; }}}Copy the code
public class Test { public static void main(String[] args) { final NameList nl = new NameList(); nl.add("aaa"); class NameDropper extends Thread{ public void run(){ String name = nl.removeFirst(); System.out.println(name); } } Thread t1 = new NameDropper(); Thread t2 = new NameDropper(); t1.start(); t2.start(); }}Copy the code
Although the following collection objects are synchronous, the program is not thread-safe.
private List nameList = Collections.synchronizedList(new LinkedList());Copy the code
This happens because, in the example above, one thread working on the list cannot prevent another thread from working on the list.
The solution to the above problem is to do a synchronization on the NameList of the operation collection object.
The rewritten code looks like this:
public class NameList { private List nameList = Collections.synchronizedList(new LinkedList()); public synchronized void add(String name) { nameList.add(name); } public synchronized String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { return null; }}}Copy the code
This way, when one thread accesses one of the synchronized methods, the other threads have to wait.
Thread deadlocks
Deadlocks are very complex for Java programs and difficult to detect. When two threads are blocked, a deadlock occurs while each thread is waiting for the other.
Let’s look at a more intuitive deadlock example:
public class DeadlockRisk { private static class Resource { public int value; } private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public int read() { synchronized (resourceA) { synchronized (resourceB) { return resourceB.value + resourceA.value; } } } public void write(int a, int b) { synchronized (resourceB) { synchronized (resourceA) { resourceA.value = a; resourceB.value = b; }}}}Copy the code
Suppose the read() method is started by one thread and the write() method by another. The reader thread will have the resourceA lock and the writer thread will have the resourceB lock, and a deadlock occurs if both persist in waiting.
In fact, the above example has a very low probability of deadlock. Because at some point in the code, the CPU must switch from the reader thread to the writer thread, deadlock basically cannot occur.
However, no matter how small the probability of a death lock in the code is, once a deadlock occurs, the program dies. There are several design approaches that can help avoid deadlocks, including the policy of always obtaining locks in a predefined order. It is beyond the scope of the SCJP exam.
Summary of thread synchronization
The purpose of thread synchronization is to protect multiple threads from damaging a resource.
2. Thread synchronization method is realized by locking. Each object has only one lock, which is associated with a specific object.
3. For statically synchronized methods, the lock is for that Class, and the lock object is the Class object of that Class. Static and non-static methods do not interfere with each other. A thread acquires the locks, which are acquired when a synchronized method on another object is accessed in a synchronized method.
4. For synchronization, being aware of which object to synchronize on is the key.
5, write thread-safe class, need to pay attention to multiple threads competing for access to resources logic and safety to make a correct judgment, analysis of the “atomic” operation, and ensure that other threads can not access competing resources during the atomic operation.
6. When multiple threads are waiting for an object lock, the thread that did not acquire the lock will block.
7. Deadlocks are caused by threads waiting for each other to lock. In practice, the probability of occurrence is very small. If you had to write a deadlock program, it might not work. However, if a program deadlocks, it will die.
Wenyuan network, only for the use of learning, such as infringement, contact deletion.
I have collected quality technical articles and experience summary in my public account “Java Circle”.
In order to facilitate your learning, I have compiled a set of learning materials, covering Java virtual machine, Spring framework, Java threads, data structures, design patterns and so on, free for students who love Java! More learning communication group, more communication problems can be faster progress ~