Chapter 2 Concurrent Access to Objects and Variables

Keywords: Java Programming jvm

Chapter 2 Concurrent Access to Objects and Variables

Label: Java Multithread Programming

<Java Multithread Programming Core Technology > Personal Notes

This chapter mainly introduces synchronization in Java multithreading, writes thread-safe programs, and solves non-thread-safe related problems.

This chapter should focus on the following technical points:

  • Use of synchronized object monitor as Object Yes
  • Use of synchronized object monitor as Class
  • How does non-thread security come about
  • Major uzoyong of non-critical volatile
  • The Difference and Use of Keyword volatile and synchronized

synchronized synchronization method

  • "Non-thread security" actually occurs when multiple threads concurrently access instance variables in the same object, resulting in "dirty reading", that is, the data retrieved is actually changed.
  • Thread safety means that the value of the instance object obtained is processed synchronously without dirty reading.

The variable in the method is thread-safe

  • The problem of "non-thread security" exists in "instance variables". If it is a private variable within a method, there is no "non-thread security" problem. The result is "thread security".

Instance variable non-thread security

  • If multiple threads jointly access instance variables in an object, a "non-thread-safe" problem may arise.
  • If there are multiple instance variables in an object accessed by threads, the results of the run are likely to cross (as demonstrated in Chapter 1).
  • If the object has only one instance variable, there may be coverage (this variable is not a local variable in the method, but a private variable belonging to the object). To solve this problem, only the keyword synchronized is added before the method.

Multiple objects, multiple locks

public class Run {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

        ThreadA athread = new ThreadA(numRef1);
        athread.start();

        ThreadB bthread = new ThreadB(numRef2);
        bthread.start();
    }
}
  • Two threads access the same name of two different instances of the same class, but the effect is asynchronous.
  • The keyword synchronized acquires a lock that is an object lock, rather than a code or method that acts as a lock.
  • Which thread executes the method with synchronized keyword first, which thread holds the lock Lock of the object to which the method belongs, then other threads can only wait, provided that multiple threads access the same object

synchronized method and lock object

  • The experimental conclusion is that the method of calling keyword synchronized declaration must be queued. In addition, we need to keep in mind the word "sharing". Only the reading and writing of shared resources need synchronization. If it is not shared resources, then there is no need for synchronization at all.

  • For different methods with the same object:

public class MyObject {
    synchronized public void methodA() {        //Retain synchronized
        try {
            System.out.println("begin methodA threadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end endTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //Public void method B () {// Remove synchronized for comparison
    synchronized public void methodB() {
        try {
            System.out.println("begin methodB threadName="
                    + Thread.currentThread().getName() + " \nbegin time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//Custom Thread A
public class ThreadA extends Thread {
    private MyObject object;
    public ThreadA(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodA();       //The methodA () method is executed
    }
}
//--------- Custom Thread B Method
public class ThreadB extends Thread {
    private MyObject object;
    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodB();        //The method B () method is executed
    }
}
//Principal function
public class Run {
    public static void main(String[] args) {
        MyObject object = new MyObject();
        ThreadA a = new ThreadA(object);
        a.setName("A");
        ThreadB b = new ThreadB(object);
        b.setName("B");
        a.start();
        b.start();
    }
}
  • Result: MethodA keeps synchronized all the time, while methodB keeps synchronized one time and another time. When methodB keeps synchronized, methodA and methodB executed by thread A and B are synchronized; when not, they are asynchronous.
  • Conclusion:
    1. Threads A hold Lock locks for object objects first, and threads B can call non-synchronized methods in object objects asynchronously.
    2. Thread A holds the Lock lock of the object object object first, and thread B needs to wait if it calls the synchronize type method in the object object object at this time, that is, synchronization.

Dirty reading

  • Although the assignment is synchronized, there may be some unexpected surprises when the assignment is made, namely "dirty Read"
  • Dirty reading occurs because getValue() is not synchronized when it gets the value, so it can be solved by adding the synchronized keyword.

synchronized lock reentry

  • The keyword synchronized has the function of lock reentry, that is, when using synchronized, when a thread gets an object lock, it can get the lock of the object again when it requests the object lock again. This also proves that locks are always available when other synchronized methods of this class are called within a synchronized method/block.
public class Service {
    //Three synchronization methods
    synchronized public void service1() {
        System.out.println("service1");
        service2();     //Method 1 calls method 2 internally, reenters from here and retrieves the lock again
    }
    synchronized public void service2() {
        System.out.println("service2");
        service3(); //Method 2 calls method 3 internally
    }
    synchronized public void service3() {
        System.out.println("service3");
    }
}
//Custom Thread Class
public class MyThread extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}
//Principal function
public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}
  • The concept of reentrant locks: you can retrieve your internal locks again. (Deadlock if you can't reentry)
  • Re-entrant locks are also supported in parent-child environments (if the method is synchronized, then the inherited method is synchronized?)

If there is an exception, the lock will be released automatically.

  • When a thread executes code that is abnormal, the lock it holds is automatically released

Synchronization has non-inheritance

  • If the method of the parent class is synchronized, the method is still synchronized after the subclass inherits.
  • But if the new method of the subclass itself uses the synchronization method of the parent class, the new method of the subclass is asynchronous except for the sentence of using the synchronization method of the parent class; if the new method of the subclass is synchronized, the keyword synchronized can be added in front of the new method.

synchronized Synchronized Statement Block

  • The synchronized declaration method has its drawbacks in some cases. For example, thread A calls the synchronized method to execute a long method, while thread B has to wait a long time. In this case, synchronized synchronized statement blocks can be used.

Drawbacks of synchronized method

Use of synchronized Synchronized Code Block

  • When a concurrent thread accesses a synchronized(this) synchronized code block in the same object, only one thread can execute the code block for a period of time after the current thread has executed the code block.

Using Synchronized Code Block to Solve the Disadvantage of Synchronization Method

public void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            String privateGetData1 = "Value 1 returned remotely after long processing of tasks threadName="
                    + Thread.currentThread().getName();
            String privateGetData2 = "Value 2 returned remotely after long processing of tasks threadName="
                    + Thread.currentThread().getName();
            //Synchronized code block
            synchronized (this) {
                getData1 = privateGetData1;
                getData2 = privateGetData2;
            }
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

Half asynchronous, half synchronous

  • Asynchronous execution is not in the synchronized block, and synchronized execution is in the synchronized block.

Synchronization between synchronized codes

  • When a thread accesses a synchronized(this) block of synchronized code in an object, access to all other synchronized(this) blocks of synchronized code in the same object will be blocked by other threads, which indicates that the "object monitor" used by synchronized is the same.
public class ObjectService {
    public void serviceMethodA() {
        try {
            synchronized (this) {   //Two synchronous blocks of code are in different ways
                System.out.println("A begin time=" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end    end=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void serviceMethodB() {
        synchronized (this) {   //Two synchronous blocks of code are in different ways
            System.out.println("B begin time=" + System.currentTimeMillis());
            System.out.println("B end    end=" + System.currentTimeMillis());
        }
    }
}
//Thread A
public class ThreadA extends Thread {
    private ObjectService service;
    public ThreadA(ObjectService service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.serviceMethodA();//Method A
    }
}
//Thread B
public class ThreadB extends Thread {
    private ObjectService service;
    public ThreadB(ObjectService service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.serviceMethodB();//Method B
    }
}
//Principal function
public class Run {

    public static void main(String[] args) {
        ObjectService service = new ObjectService();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}

Verify that the synchronized(this) block of code is locked to the current object

Use any object as an object monitor

  • Synchronized (non-this object) format: Synchronized (non-this object x) block of synchronized code

    1. With multiple threads holding "object monitor" as the same object, only one thread can execute code in synchronized (non-this object x) at the same time.
    2. When the object monitor is held as the same object, only one thread can execute synchronized (non-this object x) synchronized code block at the same time.
  • Locking non-this objects has some advantages: if there are many synchronized methods in a class, synchronized methods can be realized, but they will be blocked, which will affect the efficiency of operation; but if synchronized blocks are used to lock non-this objects, the program and synchronized methods in non-this blocks are asynchronous. Without competing with other synchronization methods for this lock, the operation efficiency can be greatly improved.

public class Service {
    private String usernameParam;
    private String passwordParam;
    //String anyString = new String(); 
    //As a private variable of a class, it's shared, the same lock

    public void setUsernamePassword(String username, String password) {
        try {
            //As a local variable, it's another lock.
            String anyString = new String();
            synchronized (anyString) {
                System.out.println("The thread name is:" + Thread.currentThread().getName()
                        + "stay" + System.currentTimeMillis() + "Enter Synchronization Block");
                usernameParam = username;
                Thread.sleep(3000);
                passwordParam = password;
                System.out.println("The thread name is:" + Thread.currentThread().getName()
                        + "stay" + System.currentTimeMillis() + "Out of Synchronization Block");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
  • Obviously, when synchronized (non-synchronized) code block format is used for synchronization operation, the object monitor must be the same object, otherwise the lock is not the same object, the result is asynchronous, and it will run cross-over.

Detailed verification of three conclusions

  • The "synchronized (non-this object x)" format is written using the X object itself as the "object monitor", so that three conclusions can be drawn:
    1. Synchronization occurs when multiple threads simultaneously execute synchronized(x) {} blocks of synchronized code.
    2. Synchronized synchronized synchronized methods in x objects are performed by other threads
    3. Synchronization occurs when other threads execute synchronized(this) code in the x object method.

Static synchronized Method and synchronized(class) Code Block

  • Keyword synchronized can also be applied to static static methods, which, if written in this way, locks Class classes corresponding to the current *. java file.
  • Both are synchronized, as is the case with adding synchronized keywords to non-static methods.
public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("The thread name is:" + Thread.currentThread().getName()
                    + "stay" + System.currentTimeMillis() + "Get into printA");
            Thread.sleep(3000);
            System.out.println("The thread name is:" + Thread.currentThread().getName()
                    + "stay" + System.currentTimeMillis() + "Sign out printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public static void printB() {
        System.out.println("The thread name is:" + Thread.currentThread().getName() + "stay"
                + System.currentTimeMillis() + "Get into printB");
        System.out.println("The thread name is:" + Thread.currentThread().getName() + "stay"
                + System.currentTimeMillis() + "Sign out printB");
    }
    synchronized public void printC() {
        System.out.println("The thread name is:" + Thread.currentThread().getName() + "stay"
                + System.currentTimeMillis() + "Get into printC");
        System.out.println("The thread name is:" + Thread.currentThread().getName() + "stay"
                + System.currentTimeMillis() + "Sign out printC");
    }
}
//------------A----------
public class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }
}
//------B---------------
public class ThreadB extends Thread {  
    @Override
    public void run() {
        Service.printB();
    }
}
//-------------C---------
public class ThreadC extends Thread {
    private Service service;
    public ThreadC(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printC();
    }
}
//Principal function
public class Run {s
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();

        ThreadC c = new ThreadC(service);
        c.setName("C");
        c.start();

    }
}
  • Essentially different: class lock and object lock:
    1. Synchronized keywords are added to static static methods to lock Class classes, while synchronized keywords are added to non-static static static methods to lock objects. These are two different locks, so they are asynchronous.
    2. Class locks work on all object instances of a class
    3. synchronized(class) code blocks act as synchronized static methods, such as: synchronized(Service.class) {}

Constant Pool Characteristics of Data Type String

  • In JVM, it has the function of String constant pool caching, so the following code result is true:
        String a = "a";
        String b = "a";
        System.out.println(a == b);
  • When both threads use the same string of locks, one of them may not be executed. Therefore, in most cases, synchronized blocks of code cannot use String as a lock object

Wireless Waiting and Solution of synchronized Method

  • When a thread calls methodA(), it will loop dead, which will prevent other threads from calling methodB().
public class Service {
    synchronized public void methodA() {
        System.out.println("methodA begin");
        boolean isContinueRun = true;
        while (isContinueRun) {
        }
        System.out.println("methodA end");
    }
    synchronized public void methodB() {
        System.out.println("methodB begin");
        System.out.println("methodB end");
    }
}
  • It could be changed to read as follows:
public class Service {
    public void methodA() {
        synchronized(object1){    //Synchronize code blocks, lock other objects
            System.out.println("methodA begin");
            boolean isContinueRun = true;
            while (isContinueRun) {
            }
            System.out.println("methodA end");
        }
    }
    public void methodB() {
        synchronized(){         //Synchronize code blocks to lock their own objects
            System.out.println("methodB begin");
            System.out.println("methodB end");
        }
    }
}

Multithread deadlock

  • Deadlock: Different threads are waiting for locks that are simply impossible to release, resulting in all tasks not being able to continue to complete
public class DealThread implements Runnable {
    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setFlag(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {      //Obtain lock1
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {      //Waiting for lock2
                    System.out.println("Press lock1->lock2 The code is executed sequentially");
                }
            }
        }
        if (username.equals("b")) {
            synchronized (lock2) {          //Obtain lock2
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock1) {      //Waiting for lock1
                    System.out.println("Press ock2->lock1 The code is executed sequentially");
                }
            }
        }
    }
}
//Principal function
public class Run {
    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            t1.setFlag("a");
            Thread thread1 = new Thread(t1);
            thread1.start();
            Thread.sleep(100);
            t1.setFlag("b");
            Thread thread2 = new Thread(t1);
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Built-in Classes and Static Built-in Classes

......

Change of Lock Object

  • When using any data type as a synchronous lock, it is important to note whether there are multiple threads holding the lock object at the same time, if they hold the same lock object at the same time, the threads are synchronous; if they get the lock object separately, the threads are asynchronous.
public class MyService {
    private String lock = "123";
    public void testMethod() {
        try {
            synchronized (lock) {       //At this point, the lock object is "123"
                System.out.println(Thread.currentThread().getName() + " begin "
                        + System.currentTimeMillis());
                lock = "456";           //Change the lock object to "456"
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "   end "
                        + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//Principal function
public class Run1 {

    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(50);       //Here, wait 50 milliseconds
        b.start();              //So the lock obtained by B is "456", A and B are asynchronous.
    }
}
  • If Thread.sleep(50) is commented out, then a and B grab "123" for synchronization.
  • As long as the object remains unchanged, even if the object's attributes are changed, the running results are synchronized

volatile keyword

  • The main function of keyword volatile is to make variables visible across threads

Keyword volatile and dead cycle

  • If not in the case of multiple inheritance, using inherited Thread classes and implementing Runnale interfaces does not make much difference in the results of program operation. If "multi-inheritance" occurs, it is necessary to deal with multi-threading problems by implementing Runnable interface.

Solving Synchronized Dead Cycle

public class PrintString implements Runnable {
    private boolean isContinuePrint = true;
    public boolean isContinuePrint() {
        return isContinuePrint;
    }
    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }
    public void printStringMethod() {
        try {
            while (isContinuePrint == true) {
                System.out.println("run printStringMethod threadName="
                        + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        printStringMethod();
    }
}
//Principal function
public class Run {
    public static void main(String[] args) {
        PrintString printStringService = new PrintString();
        new Thread(printStringService).start(); //Start threads
        System.out.println("I want to stop it. stopThread="
                + Thread.currentThread().getName());
        printStringService.setContinuePrint(false); //Set to false
    }
}
  • The function of keyword volatile is to get the value of variables from the common stack rather than from the thread private data stack.
volatile private boolean isRunning = true;
  • This synchronizes the isRunning values in the private stack with those in the public stack and forces them to be fetched from the public stack later.
  • +

Solving Asynchronous Dead Loop

  • The keywords synchronized and volatile are compared:

    1. The keyword volatile is a lightweight implementation of thread synchronization, so volatile certainly performs better than synchronized, and volatile can only modify variables, while synchronized can modify methods and code blocks.
    2. Multithread access to volatile does not block, while synchronized access does.
    3. volatile guarantees data visibility, but not atomicity; synchronized guarantees atomicity, and indirectly guarantees visibility, because it synchronizes data in private and public memory.
    4. The keyword volatile addresses the visibility of variables between threads, while the synchronized keyword addresses the synchronization of access to resources between threads.
  • Thread security includes atomicity and visibility. Java's synchronization mechanism revolves around these two aspects to ensure thread security.

Characteristics of volatile nonatomicity

i++ operations using atomic classes

  • AtomicInteger

Atomics are not completely safe, either.

synchronized code block has volatile synchronization function

  • synchronized allows multiple threads to access the same resource synchronously, and it also has the function of synchronizing private variables in thread working memory with variables in public memory.

Posted by harsh00008 on Wed, 03 Jul 2019 17:22:32 -0700