preface

  • articleSummarizes the connection between Java thread and OS thread, as well as simulated Java call OS function to create thread. From the conclusion of the previous article, we know that Java threads and OS threads are equal. We also learned why we use multithreading. Everything has advantages and disadvantages, under the advantage of multithreading to improve the efficiency of the program, it also brings another problemsynchronous. Yes, as long as the use of multithreading, we have to consider synchronization, otherwise it will be a mess! In the case of synchronization, Java has a son —synchronizedThe keyword. After JDK 1.5, it had some twins —JUCPackage under the various lock implementation. The characteristics between them will be summarized in a subsequent article.

Why use synchronization in multithreading?

  • As is the subject of the current chapter, why use synchronization in multithreading? Let’s look at the following code:
    /** * sell 20 tickets for 4 Windows */
    public class TestMulThread {
    
        private static int ticketNum = 10;
    
        public static void main(String[] args) throws InterruptedException {
    
            for (int i = 0; i < 4; i++) {
                new Thread(() -> {
                   while(! Thread.currentThread().isInterrupted() && ticketNum >0) {
                       System.out.println(Thread.currentThread().getName() + "Sold" + ticketNum-- + "Ticket"); }},"The window" + (i + 1)).start(); }}}Copy the code

    If you run the code, you’ll notice that the output is random, from negative numbers to duplicate tickets being sold (My computer CPU is 12 cores, processing speed is relatively fast, will not appear the above situation). This is clearly problematic. To solve this problem, use a synchronization strategy, which uses the synchronized keyword (Only the synchronized keyword is considered here, not the other cases). So we make a code change like this:

    /** * sell 20 tickets for 4 Windows */
    public class TestMulThread {
    
        private static int ticketNum = 10;
    
        static Lock lock = new Lock();
    
        public static void main(String[] args) throws InterruptedException {
    
            for (int i = 0; i < 4; i++) {
                new Thread(() -> {
                   while(! Thread.currentThread().isInterrupted()) {synchronized (lock) {
                           if (ticketNum > 0) {
                               System.out.println(Thread.currentThread().getName() + "Sold" + ticketNum-- + "Ticket");
                           } else{ Thread.currentThread().interrupt(); }}}},"The window" + (i + 1)).start(); }}}class Lock {}Copy the code

    The modified code creates a new Lock class as a Lock object. This completes the synchronization operation. The following summarizes the common use of synchronized keyword, classic cases and characteristics.

Two, several uses of synchronized keyword namely characteristics

  • One thing to understand here:Synchronized is an object that is locked by an identifier indicating a specific type of lock

2.1 Lock class instances and class objects

  • Please refer to the following code:
    // Lock the object
    public class Demo {
    
        private Object object = new Object();
    
        public void test(a){
            synchronized(object) { System.out.println(Thread.currentThread().getName()); }}}// Lock the current object this to lock a code block
    // Use this method to make sure that this is the same object
    // If the Demo instance is not a singleton, then this lock is basically useless for synchronization
    public class Demo {
    
        public void test(a) {
            Synchronized (this) locks an instance of the current class, in this case locking an instance of the Demo class
            synchronized (this) { System.out.println(Thread.currentThread().getName()); }}}// Lock the whole method
    // This is similar to case 2, but it locks the whole method and has a larger granularity than case 2
    public class Demo {
    
        public synchronized void test(a) { System.out.println(Thread.currentThread().getName()); }}// 
    // ===> All synchronous static methods of the current class will wait to acquire the lock when called
    // Note: You can still call the synchronized method of the class instance. Why is that?
    // Because static synchronization methods and class instance synchronization methods have different locks
    // One is a class object and the other is a class instance object.
    // Static asynchronous methods of class objects and class instances can also be called
    // Asynchronous method. Why is that? Because these methods are not locked ah, can call directly.
    public class Demo {
    
        public static synchronized void test(a) { System.out.println(Thread.currentThread().getName()); }}Copy the code

2.2 Lock the same String constant

  • Check out the following code:
    S1 and s2 are locked in the constant pool (the method area in the heap), so s1 and S2 refer to the same object. So the following test1 and test2 methods use the same lock, and the end result is that thread 2 will wait for thread 1 to release the lock before it can acquire the lock and execute the following code. * /
    public class Demo {
    
        String s1 = "lock";
        String s2 = "lock";
    
        public void test1(a) {
            synchronized (s1) {
                System.out.println("t1 start");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 end"); }}public void test2(a) {
            synchronized (s2) {
                System.out.println("t2 start"); }}public static void main(String[] args) {
            Demo demo = new Demo();
            new Thread(demo :: test1, "test1").start();
            new Thread(demo :: test2, "test2").start(); }}Copy the code

2.3 Locking Integer Objects

  • See the following code:
    public static class BadLockOnInteger implements Runnable{
        public static Integer i = 0;
    
        static BadLockOnInteger instance = new BadLockOnInteger();
    
        @Override
        public void run(a) {
            for (int j = 0; j < 10000000; j++) {
                synchronized(i) {
                    I = integer.valueof (i.intValue() + 1),
                    ValueOf () returns a new Integer object each time, resulting in the lock of the new object, of course, multithreaded synchronization failurei++; }}}public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(instance);
            Thread t2 = newThread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); }}Copy the code

2.4 Reentrancy (including inheritance)

  • Definition: Reentrancy is the continuous application for the same lock
  • See the following code
    /** One synchronous method calls another synchronous method, reentrant */ is supported
    public class Demo {
    
        public synchronized void test1(a) {
            System.out.println("test1 start");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            test2();
        }
    
        public synchronized void test2(a) {
            System.out.println("test2 start");
        }
    
        public static void main(String[] args) {
            Demo demo = newDemo(); demo.test1(); }}/** Inheritance also supports reentrant */
    public class Demo {
    
        synchronized void test(a) {
            System.out.println("demo test start");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("demo test end");
        }
    
        public static void main(String[] args) {
            newDemo2().test(); }}class Demo2 extends Demo {
    
        @Override
        synchronized void test(a) {
            System.out.println("demo2 test start");
            // The parent method is called here
            super.test();
            System.out.println("demo2 test end"); }}Copy the code

2.5 Synchronized Releases locks

  • Synchronized is the keywordManually locking automatically releases the lock. Automatic lock release at the same time includes:An exception thrown at the end of execution of a block of locked code
  • The lock is released automatically when the await method is executed.

Three, first know the object head

3.1 Object Header Structure

  • The basic uses and features of synchronized are described above. So let’s start with the object head. (Ps: To understand the synchronized keyword, understand the object header is the basis)
  • As we all know, Synchroinzed has a lock upgrade process after JDK1.6, so different locks are generated for different situations:Bias lock, lightweight lock, weight lock. These so-called locks correspond only to some information about the object header. The following two figures list the object headers in different states

3.2 How Can I View the Object Header

  • Step 1: The Maven project introduces the following JAR packages
    <dependency>
       <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.9</version>
    </dependency>
    Copy the code
  • Step 2: Create the user.java class
    public class User {
    
        public static void main(String[] args) {
            User user = newUser(); System.out.println(ClassLayout.parseInstance(user).toPrintable()); }}Copy the code
  • Step 3: Run the main method to view the object header information

3.3 Proving that hasCode is stored in the first 56 bits of the lockless object

3.3.1 CPU Small – and small-end mode

  • Why summarize this? Jol prints some object information with a lot of 0101 and the corresponding hexadecimal values. To know where HashCode exists, we need to know the size side of the CPU.

3.3.2 What is the Small-end Mode

  • Reference links:www.cnblogs.com/0YHT0/p/340…. It can be summarized as follows:Our data is stored in memory, and the way it is stored is different from CPU to CPU. If you want to store 12345678, two pairs should be used as a pair. 87 is the first and 56 is the second….. And so on. So, we know that 12 is the highest bit, so it’s going to be stored low in memory. A summary of the above links is shown in the following table:
    Memory address Stored data (Byte)
    0x00000000 0x12
    0x00000001 0x34
    0x00000002 0x56
    0x00000003 0x78
    That’s the general idea,So in big-endian mode, when the data is finally fetched (starting from low), so perfect reduction12345678. In small-endian mode, the opposite is true. I won’t summarize it here. So how do we know if our CPU is in big-endian or small-endian mode? Java provides the following apis:
    “`java
    // The output information is as follows:
    // BIG_ENDIAN: big-endian mode
    // LITTLE_ENDIAN: little endian mode
    System.out.println(ByteOrder.nativeOrder().toString());
    ` ` `

3.3.3 prove hashcode

  • Next, let’s prove that the first 56 bits of hashCode are stored.
  • Create the following class
    public class Valid {
    
        public static void main(String[] args) {
            System.out.println(ByteOrder.nativeOrder().toString());
    
            User user = new User();
            System.out.println("before hashcode");
            System.out.println(ClassLayout.parseInstance(user).toPrintable());
            // Convert hashCode to hexadecimal because jol contains hexadecimal values in the output
            System.out.println(Integer.toHexString(user.hashCode()));
    
            System.out.println("after hashcode"); System.out.println(ClassLayout.parseInstance(user).toPrintable()); }}Copy the code
  • The running results are as follows:

Four,

  • In summary, we have seen the structure of Java object headers and proved that the first 56 in unlocked state stores HashCode. A good understanding of Java’s object headers is the cornerstone of understanding the synchronized keyword. The topic of the next article is:Prove generational age, no lock, bias lock, light weight lock, heavy (chong) bias, heavy (chong) light weight lock, weight lock

  • Github address corresponding to concurrent modules:portal

  • I am a slow walker, but I never walk backwards.