Concurrent programming brings us a lot of convenience, but it also poses a thread-safety problem.
Thread Safety
Definition of thread safety:
When multiple threads access a class, the class can always represent the correct behavior, then it is called thread-safe.
The reasons for this can be summed up as follows:
1. Sharing data: Only shared data can create security issues. If it is a variable declared inside a method, it is in the virtual machine stack and is exclusive to each thread, and there is no security issue.
2. Multiple threads perform simultaneous operations on shared data. Multithreading makes simultaneous operations on the same shared data, at which point the shared data affects each other.
This is an example of a thread-safe problem:
public class UnsafeThread implements Runnable { private static int count = 0; public void increase(){ count++; } public void run() { for (int i = 0; i < 2000000; i++) { increase(); } } public static void main(String[] args) { UnsafeThread myThread = new UnsafeThread(); Thread thread1 = new Thread(myThread); Thread thread2 = new Thread(myThread); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); System.out.println(count); } catch (InterruptedException e) { e.printStackTrace(); } }}
After execution, two threads, each thread has a secondary self-increment for count, and the 2000000
expected result should be 4000000
, however, the results are basically not the right one, each time not the same, but 4000000
smaller. Why?
The steps of i++ should be:
Read-to-change-write
However, if a thread is not written back while it is being read, and the other thread is reading, then one operation is equivalent to being ineffective, resulting in fewer results than expected.
Mutual exclusion Lock
Therefore, in order to solve these problems, we think of the countermeasures:
- Eliminate shared data: The idea is good, but in some cases it is impossible to completely eliminate, and we can only reduce the sharing of data as much as possible.
- At the same time, only one thread can operate on the shared data, and the other thread waits until the thread has finished processing and then takes action.
Mutexes can solve this kind of problem.
Features of mutual exclusion locks
- The thread acquires the lock automatically before it enters the synchronization code block, and the lock is automatically released when the synchronization code block exits.
- At the same time, only one thread can hold this lock. When
A
a thread attempts to acquire a lock held by a thread, the thread B
A
must wait or block until the thread B
releases the lock. If you B
do not release this lock, you will A
need to wait all the time.
- Reentrant: When the outer function of the same thread acquires the lock, the inner recursive function still has the code to acquire the lock, but it is not affected.
Built-in lock synchronized
In this article, we discuss a built-in mutex that is provided by Java, which is used synchronized
to decorate:
synchronized(lock){ // 访问或修改共享数据 }
For just the problem, we just need to add a keyword to
public synchronized void increase(){ count++;}
The result of the final output is necessarily 4000000
.
A synchronized
decorated block of code is executed atomically (a set of statements that form an indivisible unit). Any thread that executes a synchronous code block does not see a synchronous block of code that another thread is executing that is protected by the same lock.
synchronized
There are three ways to use it:
- Normal synchronization method, the lock is the current instance object (this);
- Static synchronization method, the lock is the class object of the current classes;
- Synchronous code block, the lock is the object inside the parentheses.
Normal synchronization method, lock is the current instance object (this)
Common synchronization method, lock is the current this
object, in the above code, we verify the effect of mutual exclusion.
Verifies that the lock object in the normal method is the same.
Used if two functions increase
and decrease
normal functions are in the same object synchronized
. When a function is running, the lock has been obtained by one thread, and if another line threads to enter another function, it will not go in.
public class MyThread implements Runnable {private int state=0; private static int count = 0; Public synchronized void Increase () {System.out.println (System.currenttimemillis () + "increase begin"); try {thread.sleep (5000L); } catch (Interruptedexception e) {e.printstacktrace (); } System.out.println (System.currenttimemillis () + "Increase end"); } public synchronized void decrease () {System.out.println (System.currenttimemillis () + "decrease begin"); try {thread.sleep (10000L); } catch (Interruptedexception e) {e.printstacktrace (); } System.out.println (System.currenttimemillis () + "decrease End"); public void Run () {if (state = = 0) {state = 1; Increase (); }else{state = 0; Decrease (); }} public static void Main (string[] args) {MyThread MyThread = new MyThread (); ThreAd thread1 = new Thread (myThread); Thread thread2 = new Thread (myThread); Thread1.start (); Thread2.start (); try {thread1.join (); Thread2.join (); System.out.println (count); } catch (Interruptedexception e) {e.printstacktrace (); } }}
After the code runs, the results are as follows:
1535644833489 increase begin1535644838489 increase end1535644838489 decrease begin1535644848489 decrease end
At thread1
run time, it starts with a increase
function that pauses the milliseconds inside the function, 5000
during which time it is thread2
started, but waits for the increase
end to enter the decrease
function.
Verify that different objects are not the same as the common method lock
If the object is different, the lock is different and does not work.
In the thread that just ended up with the error, UnsafeThread
Add and synchronized
change the main
function to verify the result.
public static void main(String[] args) { UnsafeThread unsafeThread = new UnsafeThread(); UnsafeThread unsafeThread2 = new UnsafeThread(); Thread thread1 = new Thread(unsafeThread); Thread thread2 = new Thread(unsafeThread2); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); System.out.println(count); } catch (InterruptedException e) { e.printStackTrace(); }}
The output will also be 4000000
smaller than the number, because count
it is static
decorated, is a global variable, even if it is two different object operation is the same variable. And because the unsafeThread
and unsafeThread2
is two different objects, so synchronized
the lock object is not the same, the lock is not the same as the effect of mutual exclusion.
Because the lock is the current object, if the objects held by two threads are not the same, they do not act mutually exclusive.
Static synchronization method, the lock is the class object of the current classes
If the synchronized
function is on a static method, the lock is the current Class to lock. Because static members are not part of a specific object, it is not feasible to continue using this as a lock.
Verify that the same
static
Between methods, the lock is the same lock.
First, define two thread classes
public class ThreadA implements Runnable { private ThreadStaticTest threadStaticTest; public ThreadA(ThreadStaticTest threadStaticTest) { this.threadStaticTest = threadStaticTest; } public void run() { threadStaticTest.staticMethodA(); }}
public class ThreadB implements Runnable { private ThreadStaticTest threadStaticTest; public ThreadB(ThreadStaticTest threadStaticTest) { this.threadStaticTest = threadStaticTest; } public void run() { threadStaticTest.staticMethodB(); }}
Then, define a test class
public class Threadstatictest {public synchronized static void Staticmethoda () {System.out.println (Thread.curr Entthread (). GetName () + "Staticmethoda in" + System.currenttimemillis ()); try {thread.sleep (2000L); } catch (Interruptedexception e) {e.printstacktrace (); } System.out.println (Thread.CurrentThread (). GetName () + "Staticmethoda out" + System.currenttimemilli s ()); } public synchronized static void Staticmethodb () {System.out.println (Thread.CurrentThread (). GetName () + "stat Icmethodb in "+ System.currenttimemillis ()); try {thread.sleep (3000L); } catch (Interruptedexception e) {e.printstacktrace (); } System.out.println (Thread.CurrentThread (). GetName () + "Staticmethodb out" + System.currenttimemilli s ()); } public static void Main (string[] args) {threadstatictest threadstatictest = new threAdstatictest (); Thread ta = new Thread (new Threada (threadstatictest)); Thread TB = new Thread (new THREADB (threadstatictest)); Ta.setname ("Threada"); Tb.setname ("threadb"); Ta.start (); Tb.start (); try {ta.join (); Tb.join (); } catch (Interruptedexception e) {e.printstacktrace (); } }}
The results after the run are as follows:
ThreadA staticMethodA in 1535769147351ThreadA staticMethodA out 1535769149351ThreadB staticMethodB in 1535769149351ThreadB staticMethodB out 1535769152351
The results look nothing different. But in essence, the lock object is still not the same, the synchronized
keyword is added to the static method, the lock is class, and added to the non-static method, the lock is the this
object.
Verify that the static method of the same class differs from the normal method lock
First, ThreadStaticTest
add a common method inside the previous class
public synchronized void methodC() { System.out.println(Thread.currentThread().getName() + " methodC in " + System.currentTimeMillis()); try { Thread.sleep(3000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " methodC out " + System.currentTimeMillis());}
Defining a new Thread class
public class ThreadC implements Runnable { private ThreadStaticTest threadStaticTest; public ThreadC(ThreadStaticTest threadStaticTest) { this.threadStaticTest = threadStaticTest; } public void run() { threadStaticTest.methodC(); }}
Finally, add a new call to the main function
public static void main(String[] args) { ThreadStaticTest threadStaticTest = new ThreadStaticTest(); Thread ta = new Thread(new ThreadA(threadStaticTest)); Thread tb = new Thread(new ThreadB(threadStaticTest)); Thread tc = new Thread(new ThreadC(threadStaticTest)); ta.setName("ThreadA"); tb.setName("ThreadB"); tc.setName("ThreadC"); ta.start(); tb.start(); tc.start(); try { ta.join(); tb.join(); tc.join(); } catch (InterruptedException e) { e.printStackTrace(); }}
The final result is as follows
ThreadA staticMethodA in 1535769547612ThreadC methodC in 1535769547612ThreadA staticMethodA out 1535769549612ThreadB staticMethodB in 1535769549612ThreadC methodC out 1535769550612ThreadB staticMethodB out 1535769552612
As you can see, the ThreadC
thread named "Mess in", while the thread ThreadA
and also the one after the end of the ThreadB
entry. Prove ThreadA
and ThreadB
hold the same lock.
Synchronization code block, lock is the object inside the parentheses
The use of methods (static or Normal) synchronized
, followed by a decrease in performance, in the previous proof that if multiple common methods of the same object are used synchronized
, they will block each other because they hold the same lock. Therefore, it is best to use it only on methods that need to modify the shared data in the concurrency scenario .
So, how do we do it? Maybe sometimes we write a very large method, but only a small piece of it is the operation of shared data, this time in the method synchronized
is obviously not cost-effective, synchronous code block can solve this kind of problem.
synchronized(lock){ // 访问或修改共享数据 }
In a synchronized code block, synchronized
the following parentheses lock
can be any object .
The previous common method, corresponding to the
synchronized(this){ // 访问或修改共享数据 }
The static method corresponds to the
// XXX 对应的是相应的类名 synchronized(XXX.class){ // 访问或修改共享数据 }
With the use of synchronous code blocks, we can reduce performance to some extent. If there are two variables, the corresponding methods MethodA and MethodB are modified, and if you use two different lockA
and lockB
lock them, the operation will not block each other.
First, define the test class
public class Synblock {private Lock Locka = new Lock (); Private lock lockb = new Lock (); private static int CountA = 0; private static int countb = 0; public void MethodA () {synchronized (Locka) {try {System.out.println (thread.currentthr EAD (). GetName () + "MethodA Begin" + System.currenttimemillis ()); for (int i = 0; i < 2000000; i++) {counta++; } thread.sleep (1000L); System.out.println (Thread.CurrentThread (). GetName () + "MethodA End" + system.currenttimemillis ()) ; } catch (Interruptedexception e) {e.printstacktrace (); }}} public void MethodB () {synchronized (lockb) {try {System.out.printl N (Thread.CurrentThread (). GetName () + "MethodA Begin" + System.currenttimemillis ()); For (int i = 0; i < 1000000; i++) {countb++; } thread.sleep (2000L); System.out.println (Thread.CurrentThread (). GetName () + "MethodA End" + system.currenttimemillis ()) ; } catch (Interruptedexception e) {e.printstacktrace (); }}}} class Lock {} public static void Main (string[] args) {Synblock synblock = new Synblock () ; Thread Threada = new Thread (new Synblockthreada (Synblock)); Thread threadb = new Thread (new SYNBLOCKTHREADB (Synblock)); Threada.setname ("Threada"); Threadb.setname ("threadb"); Threada.start (); Threadb.start (); try {threada.join (); Threadb.join (); System.out.println ("counta=" + CountA); System.out.println ("countb=" + countb); } catch (Interruptedexception e) {e.printstacktrace (); } }}
The corresponding thread class
public class SynBlockThreadA implements Runnable { private SynBlock synBlock; public SynBlockThreadA(SynBlock synBlock) { this.synBlock = synBlock; } public void run() { synBlock.methodA(); }}
public class SynBlockThreadB implements Runnable { private SynBlock synBlock; public SynBlockThreadB(SynBlock synBlock) { this.synBlock = synBlock; } public void run() { synBlock.methodB(); }}
The final output results are as follows
ThreadA methodA begin 1535772725304ThreadB methodA begin 1535772725304ThreadA methodA end 1535772726319ThreadB methodA end 1535772727320countA=2000000countB=1000000
From the results can be seen ThreadA
and ThreadB
do not block each other.
However, if you methodB
change the in lockB
, the lockA
result is as follows
ThreadA methodA begin 1535774917415ThreadA methodA end 1535774918431ThreadB methodA begin 1535774918431ThreadB methodA end 1535774920431countA=2000000countB=1000000
The effect of mutual exclusion.
Therefore, the following code blocks are synchronized:
When multiple threads hold the same lock
(the same object in parentheses), only one thread at a time can execute synchronized
code in the synchronized code block.
synchronized
The use of this temporary end, the follow-up will be the principle of in-depth explanation and the combination of lock for more in-depth use of the explanation.
The use of synchronized in Java multithreading (v)