My god? It turns out that this is the correct implementation of multithreading

Posted by Ammar on Tue, 18 Jan 2022 07:27:02 +0100

Java Memory Model

Thread synchronization

Thread synchronization mechanism is a set of data access mechanism suitable for coordinating threads, which can ensure thread safety

The thread synchronization mechanism provided by the java platform includes lock, volatile keyword, final keyword, static keyword, and related API s such as object wait/object. notify

Lock overview

The premise of thread safety problem is that multiple threads access shared data concurrently. The concurrent access of multiple data to shared data is transformed into serial access, that is, shared data can only be accessed by one thread. Lock is this idea.

When a thread accesses data, it must first obtain a lock. The thread that obtains the lock is called the lock holding thread. A lock can only be held by one thread at a time. The code executed by the holding thread after obtaining the lock and before releasing the lock is called the critical area.

Locks are exclusive, that is, a lock can only be held by one thread. This kind of lock is called exclusive lock or mutex lock.

In the JVM part, locks are divided into internal locks and display locks. Internal locks are implemented through the Synchronized keyword, and display locks are implemented through the Synchronized keyword
java.concurrent.locks.Lock interface implementation class.

Function of lock

Lock can realize the security of shared data and ensure the atomicity, visibility and order of threads.

Locks guarantee atomicity through mutual exclusion. A lock can only be held by one thread, which ensures that the code in the critical area can only be executed by one thread at a time, so that the operations performed by the code in the critical area naturally have the characteristics of indivisibility and atomicity.

For example, all vehicles in a road section are running and executing concurrently. When passing through a road section, multiple lanes become one lane. Only one vehicle can pass through at a time, and the concurrent execution is changed to serial execution.

Visibility refers to the two actions of flushing the processor cache by the write thread and refreshing the processor cache by the read thread. The acquisition of the lock implies the action of refreshing the processor cache, and the release of the lock implies the action of flushing the processor cache.

The lock can guarantee the order. The critical area executed by the write thread in the critical area looks like it is executed completely in the order of the source code.

Related concepts of lock

Reentrant: when a thread holds the lock, it can apply for the lock again / multiple times

If a thread holds a lock and has not released it, but can continue to successfully apply for the lock, it is said that the lock can be re entered, and vice versa.

Lock contention and scheduling

Internal locks in java belong to unfair locks, and display locks support unfair locks and fair locks

Lock granularity

The number and size of shared data that can be protected is called lock granularity.

Lock protection shares a large amount of data, which is called coarse lock granularity, otherwise it is called fine granularity.

Too coarse lock granularity will cause threads to wait unnecessarily when applying for locks. Too fine lock granularity will increase the overhead of lock scheduling.

For example, if a bank has a counter where an employee can open a card, close an account, withdraw cash and loan, then everyone can only go to this counter to handle business, which will take a long waiting time. However, if the business is subdivided into one business and one counter, it will increase the bank's expenses and require three employees.

Internal lock: Synchronized

Every object in Java has an internal lock associated with it. This lock is also called monitor. It is an exclusive lock, which can ensure atomicity, visibility and exclusivity.

Synchronized(Object lock)
{
  Sync code block, where you can access shared data
}

The modified instance method is called synchronous instance method, and the modified static method is called synchronous static method.

Synchronized synchronization code block

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        for (int i = 0; i <2 ; i++) {
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();
                }
            }.start();
        }
    }
    public  void  mm()
    {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

The code of both threads is executing concurrently

Now synchronize when printing. The principle of synchronization is that the thread must first obtain the lock when executing

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        for (int i = 0; i <2 ; i++) {
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();//The object using the lock is the synchronizedLock object
                }
            }.start();
        }
    }
    public  void  mm()
    {
        synchronized (this)//this as the current object
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

Because the Synchronized internal lock is an exclusive lock, it can only be held by one thread at a time. Now Thread-0 obtains the lock object first. Thread-1 waits for Thread-0 to release the lock after execution in the waiting area, and thread-1 obtains the lock before execution.

Different lock objects cannot be synchronized

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        SynchronizedLock synchronizedLock2=new SynchronizedLock();
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();//The object using the lock is the synchronizedLock object
                }
            }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock2.mm();//The object using the lock is the synchronizedLock object
            }
        }.start();
    }
    public  void  mm()
    {
        synchronized (this)//this as the current object
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

Therefore, you must use the same lock object to synchronize

Use constants as lock objects

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        SynchronizedLock synchronizedLock2=new SynchronizedLock();
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();//The object using the lock is the synchronizedLock object
                }
            }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm();//The object using the lock is the synchronizedLock object
            }
        }.start();
    }
    public  static  final  Object obj=new Object();
    public  void  mm()
    {
        synchronized (obj)//Constant as current object
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

Synchronous instance method

Use synchronized to modify the instance method and synchronize the instance method. this is used as the lock object by default

public class SynchronizedLock {
   public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();
                }
            }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() { 
                synchronizedLock.mm2();
            }
        }.start();
    }
    //Synchronous instance method
    public  synchronized void  mm()
    {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
    }
    public  void  mm2()
    {
        synchronized (this)//Constant as current object
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

Synchronous static method

Use synchronized to modify static methods. Synchronize static methods. By default, SynchronizedLock class is used as the lock object at runtime

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm2();
            }
        }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                SynchronizedLock.mm();//The object using the lock is synchronized lock class
            }
        }.start();
    }
    //Synchronous static method
    public  synchronized static void  mm()
    {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
    public  void  mm2()
    {
        synchronized (SynchronizedLock.class)//Constant as current object
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

How to select synchronization code blocks and synchronization methods

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                try {
                    synchronizedLock.mm2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                try {
                    synchronizedLock.mm2();//The object using the lock is synchronized lock class
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    //The granularity of synchronous instance method lock is coarse, and the execution efficiency is low
    public  synchronized  void  mm() throws InterruptedException {
        long starttime= System.currentTimeMillis();
        System.out.println("start");
        Thread.sleep(3000);
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        System.out.println("end");
        long Endtime= System.currentTimeMillis();
        System.out.println(Endtime-starttime);
    }
    //Synchronous code block lock has fine granularity and high concurrency efficiency
    public  void  mm2() throws InterruptedException {
        System.out.println("start");
        Thread.sleep(3000);
        synchronized (this)//Constant as current object
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
        System.out.println("end");
    }
}

When executing the synchronization method, the two thread calls need to sleep for three seconds each time, while the synchronization code block starts the thread at the same time for three seconds, which is more efficient

Dirty reading

public class Test06 {
    public static void main(String[] args) throws InterruptedException {
        User user=new User();
        SubThread subThread=new SubThread(user);
        subThread.start();
        user.GetName();
    }
    static  class SubThread extends Thread
    {
        public User user;

        public SubThread(User user)
        {
            this.user=user;
        }

        @Override
        public void run() {
            user.SetValue("ww","456");
        }
    }
    static  class  User
    {
        private  String name="ylc";
        private  String pwd="123";
        public  void  GetName()
        {
            System.out.println(Thread.currentThread().getName()+"==>"+name+"password"+pwd);
        }
        public  void  SetValue(String name,String pwd)
        {
            System.out.println("Originally name="+this.name+",pwd="+this.pwd);
            this.name=name;
            this.pwd=pwd;
            System.out.println("Update to name="+name+",pwd="+pwd);
        }

    }
}

When the data modification is not completed, the original data is read instead of the modified data

The reason for dirty reading is that the modification and reading of shared data are not synchronized

The solution is to synchronize the modified and read methods, and add the synchronized keyword to the method

An exception occurred in the thread to release the lock

If an exception occurs to one thread in the synchronization method, will the lock not be released, and other waiting threads are waiting all the time? Demonstration:

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm();
            }
        }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm2();
            }
        }.start();
    }
    //Synchronous instance method
    public  synchronized void  mm()
    {
        for (int i = 0; i <100 ; i++) {
            if(i==50)
            {
                Integer.parseInt("abc");//Exception setting
            }
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
    public  void  mm2()
    {
        synchronized (this)
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

If an exception occurs to a thread during synchronization, the lock object will be automatically released for the next thread to continue execution

deadlock

Multiple locks may need to be used in multithreading. If the order of obtaining locks is inconsistent, it may lead to deadlock.

public class Text06_5 {
    public static void main(String[] args) {
        SubThread subThread=new SubThread();
        SubThread subThread2=new SubThread();
        subThread.setName("a"); subThread2.setName("b");
        subThread.start();subThread2.start();
    }
    static  class SubThread  extends  Thread
    {
        private  static  final Object lock1=new Object();
        private  static  final Object lock2=new Object();

        @Override
        public void run() {
            if("a".equals(Thread.currentThread().getName()))
            {
                synchronized (lock1)
                {
                    System.out.println("a thread  lock1 Got the lock,Need to get lock2");
                    synchronized (lock2)
                    {
                        System.out.println("a thread  lock2 Got the lock");
                    }
                }
            }

            if("b".equals(Thread.currentThread().getName()))
            {
                synchronized (lock2)
                {
                    System.out.println("b thread  lock2 Got the lock,Need to get lock1");
                    synchronized (lock1)
                    {
                        System.out.println(" b thread  lock1 Got the lock");
                    }
                }
            }
        }
    }
}

The program is still running, but it enters a stuck state. Thread a gets lock1. To release the thread, execute the following code to obtain lock2, but lock2 cannot be released by thread b. there is a conflict between Snipes and clams.

Avoid deadlock: when it is necessary to obtain a lock, all threads obtain the lock in the same order. Thread a locks lock1 first and then lock2. Similarly, thread b will not have a deadlock.

If you need information, please click on the praise collection and follow me. Then add a little assistant vx: bjmsb06006 to get the free download method

Topics: Java Programming Interview architecture