How are you? , I'm crooked .
A few days ago, I saw someone mention about Synchronized A question of usage , I think it's interesting , In fact, this question is also a real question I encountered when I interviewed a company three years ago , I didn't know what the interviewer wanted to test , No answer is particularly good , Later, I studied it and remembered .
So I feel very kind when I see this question , Ready to share with you :
First of all, in order to make it easier for you to reproduce the problem when you read the article , I'll give you a copy of the code that you can run directly , I hope you can take out the code and run it if you have time :
public class SynchronizedTest {
public static void main(String[] args) {
Thread why = new Thread(new TicketConsumer(10), "why");
Thread mx = new Thread(new TicketConsumer(10), "mx");
why.start();
mx.start();
}
}
class TicketConsumer implements Runnable {
private volatile static Integer ticket;
public TicketConsumer(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + " Start grabbing the first " + ticket + " Tickets , Before locking the object :" + System.identityHashCode(ticket));
synchronized (ticket) {
System.out.println(Thread.currentThread().getName() + " Get the first " + ticket + " Tickets , Successfully locked object :" + System.identityHashCode(ticket));
if (ticket > 0) {
try {
// Simulate ticket grabbing delay
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Got the first " + ticket-- + " Tickets , One less vote ");
} else {
return;
}
}
}
}
}
The program logic is also very simple , It's a simulated ticket grabbing process , altogether 10 Tickets , Open two threads to grab tickets .
Tickets are shared resources , And there are two threads to consume , So to ensure thread safety ,TicketConsumer In the logic of synchronized keyword .
This is supposed to be a beginner synchronized I always write about the examples of , The expected result is 10 Tickets , Two people rob , Only one person per ticket can get .
But the actual operation results are as follows , I only intercept the first part of the log :
There are three framed parts in the screenshot .
The top part , It's just that both of them are competing for the first place 10 Tickets , From the log output, there is no problem at all , In the end, only one person grabbed the ticket , Then go to the second 9 The process of competing for tickets .
But the framed second 9 The competition for tickets is a little confusing :
why Get the first 9 Tickets , Successfully locked object :288246497
mx Get the first 9 Tickets , Successfully locked object :288246497
Why did both of them win the first place 9 Tickets , And the objects successfully locked are the same ?
This thing , Beyond cognition .
How can these two threads get the same lock , Then execute the business logic ?
therefore , The questioner's question emerges .
- 1. Why? synchronized No entry into force ?
- 2. Why lock objects System.identityHashCode The output is the same ?
Why didn't it work ?
Let's start with a question .
First , We know very clearly from the output of the log ,synchronized In the second round 9 The ticket expired at the time .
Supported by theoretical knowledge , We know synchronized invalid , There must be something wrong with the lock .
If there is only one lock , Multiple threads compete for the same lock ,synchronized There will never be any problem .
But the two threads here do not reach the mutually exclusive condition , In other words, there is definitely more than one lock here .
This is the conclusion that we can deduce from theoretical knowledge .
Come to a conclusion first , So how can I prove “ There's more than one lock ” Well ?
Access synchronized It means you must have got the lock , So I just need to see what the locks held by each thread are .
So how to see what lock the thread holds ?
jstack command , Print thread stack function , Get to know ?
This information is hidden in the thread stack , Let's take it out and see .
stay idea How to get the thread stack inside ?
This is one in idea Inside the debugging tips , I should have appeared many times in my previous articles .
First, in order to facilitate access to thread stack information , I adjust the sleep time here to 10s:
After running, click here “ camera ” Icon :
Click a few times and there will be several corresponding click time points Dump Information :
Because I need to observe the first two locks , And every time a thread enters a lock, it waits 10s Time , So I started the project at the first 10s And the second 10s Just click once between .
In order to observe the data more intuitively , I choose to click on the icon below , hold Dump Copy the information :
There is a lot of information copied , But we just need to care why and mx These two threads can .
This is the first time Dump Related information in :
mx Thread is BLOCKED state , It's waiting for the address 0x000000076c07b058 Lock of .
why Thread is TIMED_WAITING state , It's in sleeping, It means it grabbed the lock , Executing business logic . And the lock it grabbed , You said it happened , It is mx Thread waiting 0x000000076c07b058.
From the output log , The first time I robbed tickets was really why The thread grabbed :
from Dump Look at the information , Two threads compete for the same lock , So there's nothing wrong with the first time .
good , Let's go on to the second Dump Information :
This time, , Both threads are TIMED_WAITING, All in sleeping, That means they got the lock , Into the business logic .
But take a closer look , Two threads hold different locks .
mx Locked 0x000000076c07b058.
why Locked 0x000000076c07b048.
Because it's not the same lock , So there is no competition , So you can enter synchronized Execute business logic , So both threads are in sleeping, No problem .
then , I'll take it twice Dump Put the information together for you to see , This is more intuitive :
If I use “ Lock one ” Instead of 0x000000076c07b058,“ Lock two ” Instead of 0x000000076c07b048.
So the process is like this :
why Lock a success , Execute business logic ,mx Enter lock one waiting state .
why Release the lock , Waiting for a lock mx Awakened , Hold a lock , Continue with the business .
meanwhile why Lock two succeeded , Execute business logic .
From the thread stack , We did prove that synchronized The reason why it doesn't work is that the lock has changed .
meanwhile , From the thread stack, we can also see why the lock object System.identityHashCode The output is the same .
for the first time Dump When ,ticket All are 10, among mx No lock , By synchronized Lock the .
why The thread executes ticket--
operation ,ticket Turned into 9, But at this time mx The thread is locked monitor still ticket=10 This object , It's still there. monitor Of _EntryList Waiting inside , Not because ticket Change by change .
therefore , When why After the thread releases the lock ,mx The thread gets the lock and continues to execute , Find out ticket=9.
and why Also got a new lock , You can also enter synchronized The logic of , Also found that ticket=9.
good heavens ,ticket All are 9, System.identityHashCode Can it be different ?
In theory ,why After releasing the lock, you should continue with mx Competition lock one , But I don't know where it got a new lock .
So here's the problem : Why has the lock changed ?
Who moved my lock ?
After the previous analysis , We locked the lock and it did change , When you analyze this, you get angry , rise to one 's full height and smite the table , Shout out : Which one of the melons has been locked by me ? Isn't this kengda ?
In my experience , Don't rush to throw the pot at this time , Keep looking down , You will find that the clown is himself :
After grabbing the tickets , Yes ticket--
The operation of , And this ticket Isn't it your lock object ?
At this time, you pat your thigh , See light suddenly , Say to the crowd : No big problem , Just shaking hands .
So with a big wave , Change the lock to this :
synchronized (TicketConsumer.class)
utilize class Object as the lock object , Ensure the uniqueness of the lock .
It has been verified that there is nothing wrong with it , Perfect , Finish off work .
however , Is it really over ?
In fact, why the lock object has changed , There's still a little thing left unsaid .
It's hidden in bytecode .
We go through javap command , Reverse check bytecode , You can see this information :
Integer.valueOf
What is this ?
Familiar Integer from -128 To 127 The cache of .
In other words, in our program , It will involve the process of unpacking and packing , This process will call Integer.valueOf
Method . In fact, it is ticket--
This operation of .
about Integer, When the value is within the cache range , Will return the same object . When the cache range is exceeded , Every time new A new object comes out .
This should be a necessary knowledge point of eight part essay , What do I mean by emphasizing this to you ?
It's simple , Just change the code .
I changed the number of initial votes from 10 It is amended as follows 200, Cache range exceeded , The result of the program is :
Obviously , From the first log output , The locks are not the same .
That's what I said earlier : Because it exceeds the cache range , Twice new Integer(200) The operation of , These are two different objects , Used as a lock , Just two different locks .
Revise it back to 10, To run a , You can feel it :
From the log output , There is only one lock at this time , So only one thread got the ticket .
because 10 Is a number in the cache range , So every time I get it from the cache , Is the same object .
The purpose of this paragraph is to reflect Integer Have this knowledge point , Everybody knows . But when it is mixed with other things, what problems will it cause because of this cache , You have to analyze , This is a little more effective than directly remembering the dry knowledge points .
however ...
Our initial ticket is 10,ticket--
Then the ticket became 9, It's also in the cache range , How did the lock change ?
If you have this question , Then I advise you to think again .
10 yes 10,9 yes 9.
Although they are all in cache range , But there are two different objects , The same is true when building a cache new Coming out :
Why should I add this silly explanation ?
Because when I saw other people writing similar questions on the Internet , Some articles are not written clearly , It will mislead readers into thinking “ All values in the cache range are the same object ”, This will mislead beginners .
In a word : Please don't use Integer As lock object , You can't hold .
however ...
stackoverflow
however , When I wrote the article, I was stackoverflow A similar problem is also seen in the .
The problem with this guy is : He knows that Integer Cannot be used as a lock object , But his needs seem to have to Integer As lock object .
https://stackoverflow.com/que...
Let me describe his problem to you .
First, the label is ① The place of , His program actually gets from the cache first , If not in the cache, get... From the database , Then put it in the cache .
Very simple and clear logic .
But he considered the concurrency scenario , If there are multiple threads at the same time to get the same id, But this id The corresponding data is not in the cache , Then these threads will perform the actions of querying the database and maintaining the cache .
Actions corresponding to query and storage , He used fairly expensive To describe .
Namely “ Quite expensive ” It means , To put it bluntly, this action is very “ heavy ”, It's best not to repeat .
So just let a thread execute this fairly expensive It's good to operate .
So he thought of the label ② The code of the place where .
use synchronized Come and take id Lock it , Unfortunately ,id yes Integer Type of .
Under the label ③ Where he said it himself : Different Integer object , They don't share locks , that synchronized It's no use .
In fact, his sentence is not rigorous , After the previous analysis , We know that in the cache range Integer object , They will still share the same lock , here “ share ” Competition means competition .
But obviously , His id The range must be better than Integer Large cache range .
So here's the problem : What should I do with this thing ?
When I saw this question, the first question I thought of was : It seems that I often do the above needs , How did I do it again ?
After thinking for a few seconds, I suddenly realized , Oh , Now it's all distributed applications , What I use directly is Redis Make a lock .
I haven't considered this problem at all .
If you don't use it now Redis, It's a monomer application , So how to solve it ?
Before looking at Gao Zan's answer , Let's take a look at this question first. The following comment :
The first three letters :FYI.
It doesn't matter if you don't understand , Because that's not the point .
But you know , My English level very high, So I also teach some English by the way .
FYI, Is a common English abbreviation , The full name is for your information, For reference .
So you know , He must have attached a document to you , Which translates as : Brian Goetz In his Devoxx 2018 It was mentioned in the speech that , We should not put Integer As a lock .
You can go directly to this part of the explanation through this link , Only less than 30s The second time , Practice listening casually : https://www.youtube.com/watch...
So here comes the question ?
Brian Goetz Who is it? , Why does what he says look authoritative ?
Java Language Architect at Oracle, Development Java Linguistic , Just ask if you are afraid of .
meanwhile , He is the one I have recommended many times 《Java Concurrent programming practice 》 The author of this book .
Okay , Now I have found the boss to endorse , Next, I'll show you what Gao Zan said .
The first part is not detailed , In fact, it is the points we mentioned earlier , Out-of-service Integer , It involves caching 、 Outside the cache, Barbara's ...
Focus on the underlined part , I'll translate it for you with my own understanding :
If you really have to use Integer As a lock , Then you need to make a Map or Integer Of Set, Mapping through collection classes , You can ensure that the mapping is a clear example of what you want . And this example , Then it can be used as a lock .
Then he gave such a code fragment :
Just use ConcurrentHashMap And then use putIfAbsent Method to make a mapping .
For example, multiple calls locks.putIfAbsent(200, 200), stay map There is only one value in it 200 Of Integer object , This is a map The characteristics of guarantee , There is no need for too much explanation .
But this guy is good , In case someone can't turn this corner , He explained to everyone again .
First , He said you could write like this :
But this way , You will incur a small additional cost , Every time you visit , If this value is not mapped , You will create one Object object .
To avoid that , He just keeps the integer itself in Map in . What is the purpose of this ? How is this different from directly using integers themselves ?
That's how he explained it , That's what I said earlier “ This is a map The characteristics of guarantee ”:
When you from Map In the implementation of get() when , use equals() Method to compare key values .
The difference between two identical values Integer example , call equals() The method is to determine the same .
therefore , You can pass any number of "new Integer(5)" Different Integer Instance as getCacheSyncObject Parameters of , But you will always get only the first instance passed in containing the value .
That's what it means :
Sum up a sentence : It is through Map I did the mapping , No matter what you do new How many? Integer come out , These are many Integer Will be mapped to the same Integer, So as to ensure that even if it exceeds Integer Cache range error , There is only one lock .
In addition to the high praise answer , There are two more answers. I also want to say .
The first one is this :
Don't care what he says , But when I saw this sentence translated, the tiger body shook :
skin this cat ???
It's cruel .
At that time, I thought this translation must be wrong , This must be a little slang . So I made a textual research , That's what it means :
Give you a little knowledge of English for free , You are welcome .
The second answer that should be paid attention to comes last :
This guy told you to see 《Java Concurrent programming practice 》 Of the 5.6 Section content , There's the answer you're looking for .
As it happens , I have this book at hand , So I opened it and took a look .
The first 5.6 The name of the section is “ Build efficient and scalable result cache ”:
good heavens , Let me take a closer look at this section , This is... Baby .
The sample code in your book :
.png)
Isn't it the same as the code of the man who asked the question ?
All from the cache , If you can't get it, build it again .
The difference is that the book puts synchronize Added to the method . But the book also says , This is the worst solution , Just to raise questions .
Then he resorted to ConcurrentHashMap、putIfAbsent and FutureTask A relatively good solution is given .
You can see that the problem is solved completely from another angle , Not at all synchronize Entangle in , The second method is directly removed synchronize.
After reading the plan in the book, I suddenly realized : good heavens , Although the solution given above can solve this problem , But it always feels weird , I can't tell why . It turned out to be staring at synchronize Don't put , I didn't open my mind at the beginning .
There are four pieces of code in the book , The solution is progressive , How to write it specifically , Because the book has written very clearly , I won't repeat , Just turn over the books .
If you don't have books, search online directly “ Build efficient and scalable result cache ” You can also search the original .
I'll show you the way , Let's see .
This article has been included in my personal blog , Welcome to play :
https://www.whywhy.vip/