当前位置:网站首页>When synchronized encounters this thing, there is a big hole, pay attention!

When synchronized encounters this thing, there is a big hole, pay attention!

2022-07-04 14:53:00 Why technology

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/
原网站

版权声明
本文为[Why technology]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202141239080020.html