preface

In the last article, we talked about some basic concepts of multithreading and wait notification mechanism. When we talked about the sharing of resources between threads, we mentioned that there will be data synchronization problem. Let’s first use an example to demonstrate this problem.

/ * *

 * @author : EvanZch

 *         description:

* * /


public class SynchronizedTest {



    // Assign count to an initial value of 0

    public static int count = 0;

    // add

    public void add(a) {

        count++;

    }



    public static class TestThread extends Thread {

        private SynchronizedTest synchronizedTest;



        public TestThread(SynchronizedTest synchronizedTest) {

            this.synchronizedTest = synchronizedTest;

        }

        @Override

        public void run(a) {

            super.run();

            // Perform 10000 accumulative times

            for (int x = 0; x < 10000; x++) {

                synchronizedTest.add();

            }

        }

    }

    public int getCount(a) {

        return count;

    }

    public static void main(String[] args) throws InterruptedException {

        SynchronizedTest synchronizedTest = new SynchronizedTest();

        // Start two threads

        new TestThread(synchronizedTest).start();

        new TestThread(synchronizedTest).start();

        int count = synchronizedTest.getCount();

        System.out.println("count=" + count);

    }

}

Copy the code

As you can see, we started two threads and accumulated the Count variable at the same time. Each thread accumulated the Count variable 10,000 times. We expected the result, the Count value should be 20000.

0? Why is it 0? Since we start thread execution in main, the method is sequential. When we get to the output statement, the thread run method has not been started, so we print the initial value of count 0;

How do I get the right result?

1. Wait a while to get the result

    public static void main(String[] args) throws InterruptedException {

        SynchronizedTest synchronizedTest = new SynchronizedTest();

        new TestThread(synchronizedTest).start();

        new TestThread(synchronizedTest).start();

        // Wait one second before returning to the result

        Thread.sleep(1000);

        int count = synchronizedTest.getCount();

        System.out.println("count=" + count);

    }

Copy the code

We wait a second before retrieving the result, which looks like this:

The result is no longer zero, but it’s not 20,000 as we expected, is it not enough time to wait? When we increase the wait time, we find that the result is not 20000. In this way, it is not precise to use the wait time, because there is no way to determine the end time of thread execution (in fact, thread execution is very fast, far less than a few seconds), so we can use the join method.

2, thread. The join ()

Let’s first look at the join method of thread

    / * *

     * Waits for this thread to die.

     *

     * <p> An invocation of this method behaves in exactly the same

     * way as the invocation

     *

     * <blockquote>

     * {@linkplain #join(long) join}{@code (0)}

     * </blockquote>

     *

     * @throws  InterruptedException

     *          if any thread has interrupted the current thread. The

     *          <i>interrupted status</i> of the current thread is

     *          cleared when this exception is thrown.

* /


    public final void join(a) throws InterruptedException {

        join(0);

    }

Copy the code

When the join method is called, it blocks until the thread completes its task execution.

Threads can be executed sequentially.

We can simply change the code to print the result after both threads finish executing

Note that if we call join from the main thread, the two threads will block in the main thread, but the two child threads are still processing in parallel, and will wake up the main thread to perform subsequent operations only after the completion of both threads.

    public static void main(String[] args) throws InterruptedException {

        SynchronizedTest synchronizedTest = new SynchronizedTest();

        TestThread testThread = new TestThread(synchronizedTest);

        TestThread testThread1 = new TestThread(synchronizedTest);

        testThread.start();

        testThread1.start();

        // Execute the program sequentially

        testThread.join();

        testThread1.join();

        // Get the result when both threads are finished

        int count = synchronizedTest.getCount();

        System.out.println("count=" + count);

    }

Copy the code

Results:

We use join(), which blocks (main) and wakes up the calling thread after testThread and testThread1 have finished executing. This ensures that both threads must have finished executing. However, the results are inconsistent with expectations. After multiple printing, it is found that the results have been fluctuating in the range of 10000 ~ 20000.

Why is this happening?

On an article said that the same process the process of all the resources are Shared by multiple threads, when multiple threads access member variables of an object or an object, may cause the data sync problem, such as thread A to A data, need to read from the memory and then to carry on the corresponding operation, operation to complete before you write to memory, However, if the data has not been written to the memory, thread B will also operate on the data, which will result in data synchronization problem, which is also called thread unsafe operation.

For example, thread A takes count and adds it to memory by 1. If thread B takes count and adds it to memory before adding it to memory, thread B ** takes count by 100 and adds it to memory by 1. At this point, the value is 101, so thread A’s summation is not counted, which is why the last two threads must be less than 20,000.

How can this be avoided?

We know that the reason for this is that during operations, multiple threads access an object or its member variables at the same time. To deal with this problem, we introduce the keyword synchronized

The body of the

I. Built-in lock synchronized

The keyword synchronized can be used to modify methods or in the form of synchronized blocks. It mainly ensures that multiple threads can only have one thread in a method or synchronized block at the same time. It ensures the visibility and exclusivity of thread access to variables, also known as the built-in lock mechanism.

Locks are divided into object locks and class locks:

Object lock: An object instance can be locked. There can be multiple object instances. The object locks of different object instances do not interfere with each other.

Class lock: Used to lock static methods of a Class or Class objects of a Class. We know that each Class has only one Class object, which means that there is only one Class lock.

Note:

A Class lock is a conceptual thing. It locks an object, but that object is the Class object of a Class, and it only exists.

Class and object locks do not interfere with each other.

Using the above example, we make a simple change. We add the synchronized keyword to the cumulative method and run it again.

/ * *

 * @author : EvanZch

 *         description:

* * /


public class SynchronizedTest {



    public static int count = 0;



    // We add the keyword synchronized to the add method

    public synchronized void add(a) {

        count++;

    }



    public static class TestThread extends Thread {

        private SynchronizedTest synchronizedTest;



        public TestThread(SynchronizedTest synchronizedTest) {

            this.synchronizedTest = synchronizedTest;

        }



        @Override

        public void run(a) {

            super.run();

            for (int x = 0; x < 10000; x++) {

                synchronizedTest.add();

            }

        }

    }



    public int getCount(a) {

        return count;

    }



    public static void main(String[] args) throws InterruptedException {

        SynchronizedTest synchronizedTest = new SynchronizedTest();

        TestThread testThread = new TestThread(synchronizedTest);

        TestThread testThread1 = new TestThread(synchronizedTest);

        testThread.start();

        testThread1.start();



        // Execute the program sequentially

        testThread.join();

        testThread1.join();



        int count = synchronizedTest.getCount();

        System.out.println("count=" + count);

    }

}

Copy the code

Results:

As you can see, we only add one keyword synchronized, and the result is consistent with our expectation of 20000. Synchronized

Adding to a method ensures that multiple threads operate on the method with only one thread at a time, thus ensuring thread-safety issues.

There are object locks and class locks. Let’s see how to implement them.

1.1. Object Locking

For an object instance, there can be multiple object instances. The object locks of different object instances do not interfere with each other.

Let’s make a change on the previous example.

Methods lock:

    // Non-static methods

    public synchronized void add(a) {

        count++;

    }

Copy the code

Synchronized code block lock:

    public void add(a){

        synchronized (this) {

            count ++;

        }

    }

Copy the code

Or:

    // Non-static variables

    public Object object = new Object();

    public void add(a){

        synchronized (object){

            count ++;

        }

    }

Copy the code

As you can see, object locks are essentially the same for non-static methods and variables. Let’s change our example code to verify that the object locks of different object instances do not interfere with each other.

    public static void main(String[] args) throws InterruptedException {



        SynchronizedTest synchronizedTest = new SynchronizedTest();

        // Create a SynchronizedTest object

        SynchronizedTest synchronizedTest1 = new SynchronizedTest();

        / / incoming synchronizedTest

        TestThread testThread = new TestThread(synchronizedTest);

        / / incoming synchronizedTest1

        TestThread testThread1 = new TestThread(synchronizedTest1);



        testThread.start();

        testThread1.start();

        // Execute the program sequentially

        testThread.join();

        testThread1.join();



        int count = synchronizedTest.getCount();

        System.out.println("count=" + count);

    }

Copy the code

We start two threads, pass in different instance objects, and run it several times to see the result.

Results:

The only difference is that the two threads pass in different objects. Therefore, according to the result, we can find that the object locks of different objects do not affect each other. All kinds of runs.

1.2, classes, lock

Lock the static method of a Class or the Class object of a Class. We know that each Class has only one Class object, which means that there is only one Class lock.

A class lock is also an object lock, but the object of the lock is special.

Static method lock:

    // Static method

    public static synchronized void add(a) {

        count++;

    }

Copy the code

Synchronized code block lock:

    public void add(a){

        // Pass in the Class object

        synchronized (SynchronizedTest.class){

            count ++;

        }

    }

Copy the code

Or:

    // Static member variables

    public static Object object = new Object();

    public void add(a){

        synchronized (object){

            count ++;

        }

    }

Copy the code

We know that there is only one static variable and only one Class object in memory, so we use the Class lock method to lock the add method. No matter how many objects are passed, it is also unique.

You can see that the results are in line with expectations.

Static keyword and new an object, what operation?

Static keyword:

  • A static variable is initialized when the class is loaded. It has only one in memory, and the JVM allocates memory for it only once, and all instances of the class share a static variable, that is, it changes everywhere and can be accessed directly by the class name.
  • Instance variables, on the other hand, are instantiated with new. Each instance is created with an instance variable, which lives and dies with that instance.

New an object, what does the bottom layer do? Static initialization (1 static code block and 2 static variables) 3. Member variable initialization (1 normal code block and 2 normal member variables) 4. Constructor initialization (constructor)