当前位置:网站首页>Thread safety analysis of [concurrent programming JUC] variables

Thread safety analysis of [concurrent programming JUC] variables

2022-07-01 08:15:00 Looking at the stars

Whether member variables and static variables are thread safe ?

  • If they don't share , Then thread safety
  • If they're shared , Depending on whether their state can be changed , There are two more cases
    • If there are only read operations , Then thread safety
    • If there are read and write operations , Then this code is a critical area , Thread safety needs to be considered

Local variables are thread safe ?

  • Local variables are thread safe
  • But the objects referenced by local variables may not be
    • If the object does not escape the function of the method, access , It's thread safe
    • If the object escapes the scope of the method , Thread safety needs to be considered

1 Local variable analysis

Test code :

@Slf4j(topic = "test4")
public class test4 {
    
    public static void test() {
    
        int i = 10;
        i++;
    }
}

Bytecode ( Compile the code first , Decompiling again :javap -v test4.class)

public static void test();
  descriptor: ()V
  flags: ACC_PUBLIC, ACC_STATIC
  Code:
    stack=1, locals=1, args_size=0
       0: bipush        10
       2: istore_0
       3: iinc          0, 1
       6: return
    LineNumberTable:
      line 18: 0
      line 19: 3
      line 20: 6
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          3       4     0     i   I

Call per thread test() When the method is used , local variable i Multiple copies will be created in the stack frame memory of each thread , So there is no sharing .
 Insert picture description here

2 Comparative analysis of member variables and local variables

ThreadUnsafe Class has member variables list And three ways ,method2 The way is to list Add elements to it ,method3 It's from list Remove elements from .

When two threads work together on the same list Object time , The following will happen : Threads 1 The added element is not written into memory by the thread 2 interrupt , Threads 2 Add elements and write them into memory , After that thread 1 Then write it into memory , Two additions are equivalent to only one addition , Then remove it twice , There will be errors .

public class TestThreadSafe {
    
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;

    public static void main(String[] args) {
    
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
            new Thread(() -> {
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}

class ThreadUnsafe {
    
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
    
        for (int i = 0; i < loopNumber; i++) {
    
            method2();
            method3();
        }
    }

    private void method2() {
     list.add("1"); }
    private void method3() {
     list.remove(0); }
}

Report errors IndexOutOfBoundsException

Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.remove(ArrayList.java:498)
	at Chapter4.ThreadUnsafe.method3(TestThreadSafe.java:39)
	at Chapter4.ThreadUnsafe.method1(TestThreadSafe.java:34)
	at Chapter4.TestThreadSafe.lambda$main$0(TestThreadSafe.java:23)
	at java.lang.Thread.run(Thread.java:748)

analysis :

  • No matter which thread method2 All references are in the same object list Member variables
  • method3 And method2 The analysis is the same

 Insert picture description here
When put list Change the member variable to local variable :

public class TestThreadSafe {
    
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;

    public static void main(String[] args) {
    
        ThreadSafe test = new ThreadSafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
            new Thread(() -> {
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}
class ThreadSafe{
    
    public void method1(int loopNumber) {
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
     list.add("1"); }
    private void method3(ArrayList<String> list) {
     list.remove(0); }
}

analysis :

  • list It's a local variable , Each thread creates a different instance when it calls , No sharing
  • and method2 The parameters are from method1 It's from , And method1 Reference the same object in
  • method3 Parameter analysis and method2 identical
  • method2 and method3 It's all private methods , operation list There are only these two methods .
     Insert picture description here

3 Local variable exposure reference

Thinking about method access modifiers , If you put method2 and method3 The method of is modified to public Will there be proxy thread safety issues ?

  • situation 1: There are other threads calling method2 and method3
  • situation 2: In the case 1 On the basis of , by ThreadSafe Class to add subclasses , Subclass coverage method2 or method3 Method , namely
class ThreadSafe{
    
    public void method1(int loopNumber) {
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
     list.add("1"); }
    public void method3(ArrayList<String> list) {
     list.remove(0); }
}

take method2 and method3 The permission modifier of is changed to public, Still thread safe at this point . because , Calling method2 and method3 yes , Operation of the list It's not in method1 Internal list.

Add subclasses ThreadSafeSubClass Inherit ThreadSafe, And rewrite method3 Method , Create a thread inside it to operate list.

class ThreadSafe{
    
    public void method1(int loopNumber) {
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
     list.add("1"); }
    public void method3(ArrayList<String> list) {
     list.remove(0); }
}

class ThreadSafeSubClass extends ThreadSafe{
    
    @Override
    public void method3(ArrayList<String> list) {
    
        new Thread(() -> {
    
            list.remove(0);
        }).start();
    }
}

At this point, there will be a thread safety problem , because method1 establish list The object operation list, call method3 Another thread will be created to operate this list, Multiple threads share list, There will be thread safety issues .

As can be seen from the example private and final Provide 【 Security 】 The significance of .

private Methods that modify cannot be overridden .
final Prevent subclasses from overriding methods .

4 Common thread safety classes

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent Class under package

By saying that they are thread safe, I mean , When multiple threads call a method of their same instance , It's thread safe . It can also be understood as

  • Each of their methods is atomic
  • But note that the combination of their multiple methods is not atomic , See the analysis later

5 Combination of thread safe class methods

Analyze whether the following code is thread safe ?

Hashtable table = new Hashtable();
//  Threads 1, Threads 2
if (table.get("key") == null) {
    
    table.put("key", "value");
}

The possible execution order is as follows :
 Insert picture description here

Ideally , There may only be one thread put; But it's also possible that a thread put End , By another thread put Cover . Cover the previous situation , Missing updates .

Every method in a thread safe class can guarantee atomicity , But composition does not guarantee atomicity . If you want the combination to ensure atomicity , You need to add a protection on the outside of the combination .

6 Immutable thread safety

String、Integer And so on are immutable classes , Because of its internal state ( attribute ) Can't change ( Only read , Can't change ), Therefore, their methods are thread safe

but ,String Yes replace, substring And other methods can change the value , So how do these methods ensure thread safety ?

see String Source code , Among them substring Method creates a new string , It is not a modification of the original string .

public String substring(int beginIndex) {
    
    if (beginIndex < 0) {
    
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
    
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

7 The example analysis

example 1
 Insert picture description here
Servlet Running on the Tomcat In the environment , There is only one example , Will be tomcat Shared by multiple threads , Therefore, there will be sharing problems among the member variables .

  • Map map: Not thread safe , Non thread safe class ,HashTable It's thread safe .
  • String S1: Thread safety , Because it is an immutable class .
  • final String S2: Same thread safety .
  • Date D1: Not thread safe , Non thread safe class .
  • final Date D2:Date The object of D2 The reference value of cannot be changed , But the attribute in the date ( Specific date ) You can change , Not thread safe . An immutable reference to an object does not mean that the state of the object is immutable .

example 2
 Insert picture description here
 Insert picture description here
From the bottom up :

  • DAO No member variables , Even if multiple threads access , There is no state to modify , So it's thread safe .
  • Connection Is a local variable in a method , Even if there are multiple threads , Other threads cannot access ( Each thread creates a separate Connection), It's also thread safe .
  • Service It contains private member variables , But there is nothing else to change it , It is immutable , So it's thread safe .

example 3
 Insert picture description here
here Connection by DAO Local variables of ,Servelet Only one copy. , therefore Service Only one copy. , therefore DAO Only one copy. , therefore DAO It must be shared by multiple threads , So the variables will be shared by multiple threads . therefore , Not thread safe .

such as , Threads 1 You just created Connection, It's not working yet , Threads 2 Just take Connection Use , After using close, Then there is something wrong with the opportunity .

example 4
 Insert picture description here
And example 4 comparison ,Dao identical , but Service Different . On each call Service when , Will recreate a new Dao, No will Dao As a Service Member variables of .

Dao by Service Local variables of inner methods , Each new thread creates a new Dao, new Dao It will create a new one Connection, So there will be no thread safety issues . But this way is not very good .

example 5
 Insert picture description here
although SimpleDateFormat Is the script variable , But it can be achieved through abstract methods foo Exposed to the outside . Its subclasses may have done some improper methods .

among foo I'm not sure about your behavior , May lead to unsafe occurrence , It's called Extraterrestrial method .
 Insert picture description here
Methods or properties that do not want to be exposed are set to final.String That's what the implementation of .

8 exercises

Selling tickets

@Slf4j(topic = "ExerciseSell")
public class ExerciseSell {
    
    public static void main(String[] args) throws InterruptedException {
    
        //  Simulate multiple people buying tickets 
        TicketWindow window = new TicketWindow(1000);

        //  Statistics of the number of tickets sold 
        List<Integer> amountList = new Vector<>();

        //  A combination of all threads 
        List<Thread> threadList = new ArrayList<>();

        //  Selling tickets 
        for (int i = 0; i < 100; i++) {
    
            Thread thread = new Thread(() -> {
    
                //  Buy tickets 
                int amount = window.sell(randomAmount());
                try {
    
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
    
                    e.printStackTrace();
                }
                amountList.add(amount);
            });
            threadList.add(thread);
            thread.start();
        }

        //  Wait for the thread to finish running 
        for (Thread thread : threadList) {
    
            thread.join();
        }
        //  Count the number of votes sold and the number of remaining votes 
        log.debug(" Surplus ticket :{}", window.getCount());
        log.debug(" Number of tickets sold :{}", amountList.stream().mapToInt(i->i).sum());
    }

    // Random  Thread safe 
    static Random random = new Random();

    //  Random 1-5
    public static int randomAmount() {
    
        return random.nextInt(5) + 1;
    }
}

/** *  Ticket window  */
class TicketWindow {
    
    private int count;

    public TicketWindow(int count) {
    
        this.count = count;
    }

    public int getCount() {
    
        return count;
    }

    public int sell(int amount) {
    
        if (count >= amount) {
    
            count -= amount;
            return amount;
        } else {
    
            return 0;
        }
    }
}

Running more than once may cause errors : The number of votes sold + The number of votes left != Initial votes

Find the critical zone , A critical region : Multithreaded read and write operations on shared resources

Selling tickets window Inside count It's a shared variable

int amount = window.sell(randomAmount());

This is also a shared variable , But it's thread safe .

amountList.add(amount);

So as long as the shared variables involved in the ticket selling action are thread safe , add to synchronized

public synchronized int sell(int amount) {
    
    if (count >= amount) {
    
        count -= amount;
        return amount;
    } else {
    
        return 0;
    }
}
原网站

版权声明
本文为[Looking at the stars]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202160134355334.html