Java concurrency with the synchronized keyword


In the last article we introduced the basic idea of concurrency and the basics of threading, through multi-threading we can achieve the full use of computer resources, but at the end we also explained the two typical problems that multi-threading brings to the program, for which the synchronized keyword can be a good solution. The introduction to synchronized contains some of the following main elements.

  • synchronized modifies the instance method
  • synchronized modifies static methods
  • synchronized modifies the code block
  • Using synchronized to solve the race condition problem
  • Using synchronized to solve memory visibility problems

I. Use the synchronized keyword to modify the instance method In our Java, each object has a lock and two queues, one for hanging threads that have not acquired the lock and one for hanging threads that have to wait because the conditions are not met. And our synchronized is actually an integration of locking and releasing locks. Let's start with an example.

 /* Define a counter class */
public class Counter {
    private int count;

    public synchronized int getCount(){return this.count;}

    public synchronized void addCount(){this.count++;}
}
/*Definition I threads kind*/
public class MyThread extends Thread{

    public static Counter counter = new Counter();

    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        counter.addCount();
    }
}
/*main approach launch100 threads*/
public static void main(String[] args){
        Thread[] threads = new Thread[100];
        for (int i=0;i<100;i++){
            threads[i] = new MyThread();
            threads[i].start();
        }

        for (int j=0;j<100;j++){
            threads[j].join();
        }

        System.out.println(MyThread.counter.getCount());
    }

The above program has the same result no matter how many times it is run.

This is a typical use ofsynchronized Example of keyword-modified instance method to solve a competing condition problem。 First in the thread class we defined, We define aCounter an actual example, Then let every subsequent threads All run with random sleep first, Then call this public variablecount The self-increasing method of, Only that the self-increasing method is withsynchronized keyword-modified。 We said that every object has a lock and two queues, Here.count An instance is an object, This hundred threads Every time you wake up from a nap you have to callcount ofaddCount approach, And all calls to theaddCount method threads must all first obtain thecount Lock for this object, that is say, If there is a threads Got it.count object's lock and start callingaddCount methodological, All other threads have to block on a queue for that object, Wait for the thread that obtained the lock to finish executing to release the lock。

consequently, at the same moment, There could only be one threads acquirecount locks and self-incrementing operations on them, All other threads are waiting on the object's blocking queue, Naturally, there will be no more threads A situation where the data value of a variable is incorrect due to simultaneous manipulation of the same variable at a certain time period。

Second, use the synchronized keyword to modify the static method For static methods, it's actually similar to instance methods. Only the synchronized keyword for instance methods, it gets a lock on the instance object, and all threads sharing the same object must first get a lock on that object. And for static methods, the synchronized keyword gets a lock on the class, which means that for all threads that need to access the same class are required to get a lock on that class first, otherwise they will need to wait on some blocking queue.

/*Definition I threads kind*/
public class MyThread extends Thread{

    public static int count;

    public synchronized static void addCount(){
        count++;
    }
    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        addCount();
    }
}
/* launch100 threads*/
public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[100];
        for (int i=0;i<100;i++){
            threads[i] = new MyThread();
            threads[i].start();
        }

        for (int j=0;j<100;j++){
            threads[j].join();
        }

        System.out.println(MyThread.count);
    }

The program is basically similar to our first example, in the thread class we define a static variable and a static method that is modified by the synchronized keyword, and then the run method still puts the current thread to sleep randomly and then calls this static method that is modified by the synchronized keyword. We can see that no matter how many times we run the program, the result is the same.

each threads After waking up from a nap, It's all going to calladdCount approach, And calling that method presupposes getting the classCount locks, If you can't get it you must wait on the object's blocking queue。 So there will only be one at a time threads invokeaddCount approach, Naturally, no matter how many times it runs, The results will all be100。

Third, use the synchronized keyword to modify the code block Using the synchronized keyword to modify a block of code is slightly different from the two cases described above. For instance methods, the synchronized keyword always tries to get a lock on some object, for static methods, the synchronized keyword always tries to get a lock on some class, and for our code block, it would need to explicitly specify who to use as the lock. For example.

/*Definition I threads kind*/
public class MyThread extends Thread{

    public static Integer count = 0;

    @Override
    public void run(){
        try {
            Thread.sleep((int)(Math.random()*100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (count){
            count++;
        }
    }
}

In the thread class we defined, We define a static variablecount, And every threads After waking up are going to try to get a lock on that object, If you can't get it, block on the object's blocking queue and wait for the lock to be released。 Actually, here.synchronized The keyword utilized is the objectcount locks, The two forms we described above,synchronized Keyword modifiers on instance methods and static methods, Locks on class objects and locks on classes are utilized by default。 for example:

public synchronized void show(){....} 

Calling the show method is equivalent to.

synchronized(this){
    public void show(){...}
}

And for static methods.

public class A{
    public synchronized static void show(){....}
}

Equivalent to.

synchronized(A.class){
    public static void show(){....}
}

IV. Using the synchronized keyword to solve the memory visibility problem By learning about thesynchronized Three different scenarios of application, We should have a general idea of what it is。 Here we use it to solve one of the problems with multithreading mentioned in the previous post ----- Memory visibility issues。 As for the issue of the race condition, it is indirectly covered in the first subsection, No further elaboration here。 Let's briefly repeat the memory visibility issue here, Because ourCPU There's a cache., So when a threads At the time of running, Some variable value changes are not immediately written back to memory, Instead, it is cached in all levels of cache, This causes other threads to access this public variable without getting the latest value, Thus leading to deviations in the values of the data, Inaccurate calculations。 Let's look at an example:

/*Definition I threads kind, and define a shared variablecount*/
public class MyThread extends Thread{

    public static int count = 0;

    @Override
    public void run(){
        while (count==0){
            //running
        }
        System.out.println("mythread exit");
    }
}
/*main function launch one threads*/
public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();

        Thread.sleep(1000);

        MyThread.count = 1;
        System.out.println(MyThread.count);
        System.out.println("exit main");

    }

We defined a shared variable in the thread class we defined, the main job of the run method is to loop and wait for count not to be 0. And we modified the value of this count in the main thread, because the operation of looping is more frequent to determine the condition, so the thread does not take the value of count from memory every time, but in its cache, so the main thread's modification of count is always invisible in the thread thread. So the output of our program is as follows.

After the main thread modifies the value of count, the output shows that indeed the value of count is 1. Then the main thread exits, but we find that the program does not end and the exit message of the thread is not printed. That means the threadthread is still trapped in the while loop, even though the main thread has modified the value ofcount. This is a memory visibility problem, mainly because the bridge between multiple threads is memory, and each thread has its own internal cache, so if changes to public variables are not updated to memory in a timely manner, then it is easy for other threads to access data that is not up to date.

We use the synchronized keyword to solve the above problem.

public class MyThread extends Thread{

    public static int count = 0;

    public synchronized static int returnCount(){return count;}
    
    @Override
    public void run(){
        while(returnCount()==0){

        }
        System.out.println("mythread exit");
    }
}

We use the synchronized key to modify a method that returns the value of count. Two of jvm's rules for synchronized are that a thread must flush all shared variables into memory before unlocking them, and that a thread will clear all caches when it releases a lock forcing this thread to read from memory when it uses that shared variable. This ensures that every read of a shared variable is up to date.

Of course it would be a bit of an overkill to use the synchronized keyword just to solve the memory visibility problem. After all, the cost overhead of synchronized is relatively large. Java provides a volatile keyword for solving this memory visibility problem. For example.

public static volatile int count = 0;

As such, we can simply prefix a variable with the modifier volatile to allow the variable to be fetched from memory when it is read, i.e. to keep the data value up to date to solve the memory visibility problem.

So far, we've briefly covered some basic uses of the synchronized keyword, introduced the scenarios it can modify, and used it to solve two of our typical multi-threaded problems. In the next article we will focus on the mechanism of collaboration between threads.


Recommended>>
1、Our school institute held the sixth Chongqing Learning Forum in 2018
2、Love Your BugLinux China
3、Southeastern Motors enters the Wing 3 this year
4、Innovative AI technology leads the way in mobile chip development Kirin 970 wins China Good Design Gold Award
5、Nature superconducting synapses surpass human brains ability to process information

    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号