Class variable use in multi-threaded environment

Static variable

When modifying static variables in Java, you may encounter the following problems:
Static variables belong to the entire class, not instances of the class. If multiple threads modify the value of a static variable at the same time, race conditions and inconsistent results may result.

1. Multi-thread competition problem: If multiple threads modify the value of static variables at the same time, it may cause race conditions and inconsistent results.
public class StaticVariableExample {<!-- -->

    private static int counter = 0;

    public static void main(String[] args) {<!-- -->
        Thread thread1 = new Thread(() -> {<!-- -->
            for (int i = 0; i < 1000; i ++ ) {<!-- -->
                counter ++ ; // Thread 1 increases the value of counter
            }
        });

        Thread thread2 = new Thread(() -> {<!-- -->
            for (int i = 0; i < 1000; i ++ ) {<!-- -->
                counter--; // Thread 2 decreases the value of counter
            }
        });

        thread1. start();
        thread2.start();

        try {<!-- -->
            thread1. join();
            thread2. join();
        } catch (InterruptedException e) {<!-- -->
            e.printStackTrace();
        }

        System.out.println("counter value:" + counter);
    }
}

In the above example, two threads modify the value of the static variable counter at the same time, one increases and the other decreases. Since the operations of the two threads are not synchronized, the final result is indeterminate and may get different results each time it is run.

2. Visibility issues: In a multi-threaded environment, if a thread modifies the value of a static variable, other threads may not be able to see the change immediately.
public class StaticVariableExample {<!-- -->

    private static boolean flag = false;

    public static void main(String[] args) {<!-- -->
        Thread thread1 = new Thread(() -> {<!-- -->
            flag = true; // Thread 1 modifies the value of flag
        });

        Thread thread2 = new Thread(() -> {<!-- -->
            while (!flag) {<!-- -->
                // Thread 2 waits in a loop until the value of flag becomes true
            }
            System.out.println("flag value has changed to true");
        });

        thread1. start();
        thread2.start();

        try {<!-- -->
            thread1. join();
            thread2. join();
        } catch (InterruptedException e) {<!-- -->
            e.printStackTrace();
        }
    }
}

In the above code, thread 1 modifies the value of the static variable flag to true, and thread 2 checks the value of flag in the loop waiting. Without proper synchronization mechanisms, such as using the volatile keyword or synchronized blocks/methods, thread 2 could get stuck in an infinite loop because it cannot see thread 1’s modification of flag.

Static immutable variables

In Java, static immutable variables are constants that are shared and unmodifiable throughout the class, and they are declared using the static final keyword. Static immutable variables have the following characteristics:

1. Sharing: Static immutable variables are at the class level, and all instances and methods can access them. They share the same value throughout the class and their value does not change no matter how many instances of the class are created.

2. Immutability: Once a static immutable variable is initialized and assigned, its value cannot be modified. This makes them a good choice for representing constant values.

Note: When the variable type is not a basic data type; such as:

private static final Map<String, Integer> MAP = new HashMap<>();

Although the variable modified by static final is itself immutable, if the Map object itself is mutable, it may still be possible to modify its content. This means that although the Map object referenced by the MAP variable cannot be modified, the contents of the Map can be changed through the object.

public class StaticFinalMapExample {
    private static final Map<String, Integer> MAP = new HashMap<>();

    public static void main(String[] args) {
    // MAP = new HashMap<>(); // Compile error, unable to modify immutable Map variable
        MAP.put("key", 10); // Compilation passed, add the key-value pair to the Map
        System.out.println(MAP); // output: {key=10}
        
        MAP.put("key", 20); // Compile passed, modify the value corresponding to the key in the Map
        System.out.println(MAP); // output: {key=20}
    }
}

In a multi-threaded environment, you may face the following problems:

1. Visibility issues: In a multi-threaded environment, if a thread modifies the content of the static final Map, other threads may not be able to see the change immediately. This is because there is no synchronization mechanism in place to guarantee that modifications to the Map are visible across all threads.
public class StaticFinalMapExample {
    private static final Map<String, Integer> MAP = new HashMap<>();

    public static void main(String[] args) {
        MAP.put("key", 10); // Initialize static Map
        
        Thread thread1 = new Thread(() -> {
            MAP.put("key", 20); // Thread 1 modifies the value of Map
        });

        Thread thread2 = new Thread(() -> {
            int value = MAP.get("key"); // Thread 2 reads the value of Map
            System.out.println("value:" + value); // may output the old value 10 instead of the modified value 20
        });

        thread1. start();
        thread2.start();
    }
}

In the above code, thread 1 modifies the value of MAP, and thread 2 may not see this change immediately when reading the value of MAP. This could cause thread 2 to read the old value of 10 instead of the modified value of 20.

2. Concurrent modification problem: If multiple threads try to modify the content of the static final Map at the same time, it may cause a concurrent modification exception (ConcurrentModificationException) or cause data inconsistency.
public class StaticFinalMapExample {
    private static final Map<String, Integer> MAP = new HashMap<>();

    public static void main(String[] args) {
        MAP.put("key", 10); // Initialize static Map
        
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i ++ ) {
                MAP.put("key", i); // thread 1 modifies the value of Map
            }
        });

        Thread thread2 = new Thread(() -> {
            int value = MAP.get("key"); // Thread 2 reads the value of Map
            System.out.println("value:" + value); // The output may be uncertain, and a ConcurrentModificationException (concurrent modification exception) exception may be thrown
        });

        thread1. start();
        thread2.start();
    }
}

Instance variables

Instance variables are variables declared in a class whose value is associated with each instance of the class. Each instance of a class has its own independent copy of instance variables whose values can change during the lifetime of the object. Instance variables are not decorated with the static keyword, so they are object-level variables.

public class InstanceVariableExample {<!-- -->
    private String name; // instance variable
    private int age; // instance variable

    public void setName(String name) {<!-- -->
        this.name = name; // Set the value of the instance variable name
    }

    public void setAge(int age) {<!-- -->
        this.age = age; // Set the value of the instance variable age
    }

    public void displayInfo() {<!-- -->
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }

    public static void main(String[] args) {<!-- -->
        InstanceVariableExample obj1 = new InstanceVariableExample();
        obj1.setName("John");
        obj1. setAge(25);
        obj1.displayInfo();

        InstanceVariableExample obj2 = new InstanceVariableExample();
        obj2.setName("Jane");
        obj2. setAge(30);
        obj2.displayInfo();
    }
}

When Java instance variables are modified, the following problems may arise:

  1. Inconsistent state: If an instance variable is modified in multiple methods without proper synchronization or control, it may result in inconsistent values of the instance variable in different places. This may lead to undefined program behavior or erroneous results.

  2. Thread safety issues: If multiple threads modify the same instance variable at the same time without proper synchronization, thread safety issues may arise, such as race conditions (Race Condition), data race (Data Race), etc. This can lead to issues like data corruption, deadlocks, infinite loops, etc.

  3. Concurrent access issues: If an instance variable is accessed simultaneously by multiple threads without proper synchronization or control, concurrent access issues may result. This can lead to data inconsistencies or unexpected results.

  4. Maintainability issues: When instance variables are frequently modified, the code can become difficult to understand and maintain. Excessive modification may increase the complexity of the code, making the program difficult to debug and modify.

To avoid these problems, the following measures can be taken:

  1. Declare instance variables as private and provide public access methods (getters and setters) to control access to instance variables.

  2. In a multi-threaded environment, use synchronization mechanisms (such as the synchronized keyword, lock objects, etc.) to ensure safe access to instance variables.

  3. Minimize the direct modification of instance variables, and recommend using immutable objects or methods to modify and manipulate data.

  4. Carry out appropriate encapsulation and abstraction according to needs, organize related states and behaviors together, and improve the readability and maintainability of the code.

In short, rational use of instance variables, and appropriate access control and synchronization mechanisms can avoid potential problems and improve the robustness and reliability of the program.

Counterexample: Shows the problems that may be caused by modifying instance variables without proper synchronization mechanism: the problem is that when multiple threads call the increment() method at the same time, since there is no synchronization mechanism to protect the value of count Access, a race condition (Race Condition) may occur: Since the increment() method is not synchronized, multiple threads may read and modify the count variable at the same time, resulting in inaccurate counting. The output may not be the expected 2000, but a value less than 2000.

public class Counter {<!-- -->
    private int count;

    public int getCount() {<!-- -->
        return count;
    }

    public void increment() {<!-- -->
        count + + ;
    }
}



public class test1 {<!-- -->
    public static void main(String[] args) throws InterruptedException {<!-- -->
    // create an instance
        Counter counter = new Counter();

// thread 1
        Runnable runnable1 = () -> {<!-- -->
            for (int i = 0; i < 1000; i ++ ) {<!-- -->
                counter. increment();
            }
        };

// thread 2
        Runnable runnable2 = () -> {<!-- -->
            for (int i = 0; i < 1000; i ++ ) {<!-- -->
                counter. increment();
            }
        };

        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);

        thread1. start();
        thread2.start();

    // Wait for both threads to finish executing
        thread1. join();
        thread2. join();

        System.out.println(counter.getCount()); // The output may not be 2000

    }
}