Will Java Spring Boot Set and Get Values in Redis in a Thread Safe Way?

geekTechnique :

I need to store the values of an ArrayList that changes frequently and persist those values in case of an application crash. The application I'm working on uses a Redis database already, so it seemed like a good choice.

Below, I have boiled down a minimal example of a spring boot controller that connects to a localhost instance of Redis and uses it to store serialized objects. The value can be modified from a controller endpoint, or through a scheduled job that runs every 5 seconds. If you do a series of get-requests to localhost:8080/test, you'll see the scheduled job remove items from the ArrayList one at a time.

Is it possible for a value to get missed, or for something not thread-safe to happen here? I'm concerned a scheduled job might conflict with changes made from the controller endpoint if they try to modify the object or set the Redis value at the same time, especially if the network slows down, but I'm unsure if that would actually be a problem. Everything seems to work fine as it runs on my localhost, but I remain skeptical.

I read this article, among others, on thread safety, but it didn't answer if any of those things are even necessary for this particular situation. I'm also aware that Redis read and writes are atomic, but I thought, what if the commands get sent to Redis in the wrong order?

I was thinking that if this implementation has problems, then Lombok's @Syncronized annotation might be useful for an abstracted out method for IO. I appreciate any input and time spent.

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;

@RestController
public class Example {

    RedisClient redisClient = RedisClient.create("redis://localhost:6379/");
    StatefulRedisConnection<String, String> connection = redisClient.connect();
    RedisCommands<String, String> redis = connection.sync();

    Gson gson = new Gson();

    ArrayList<String> someList = new ArrayList<>();


    public Example() {
        if(redis.exists("somekey") == 1){
            Type collectionType = new TypeToken<Collection<VideoDAO>>(){}.getType();
            someList = new ArrayList<>(gson.fromJson(redis.get("somekey"), collectionType));
        }
    }

    @GetMapping("/test")
    public void addToSomeList(){
        someList.add("sample string");
        redis.set("somekey",gson.toJson(someList));
        System.out.println("New item added. " + someList.size() + " items in array");
    }

    @Scheduled(fixedRate = 5000)
    public void popFromSomeList() {
        if (!someList.isEmpty()) {
            someList.remove(0);
            redis.set("somekey", gson.toJson(someList));
            System.out.println("Item removed. " + someList.size() + " items in array");
        }
    }

}

I'm using java 1.8.

Kayaman :

Most obviously someList isn't thread-safe, so even if you ignore Redis the code is broken.

Let's say we make it thread-safe with Collections.synchronizedList(new ArrayList<>());. Then add and pop still aren't atomic, although that might not matter too much for the functionality. You could just end up with (for example) the following kind of execution

someList.add("sample string");
someList.remove(0);
redis.set("somekey", gson.toJson(someList));
redis.set("somekey", gson.toJson(someList));

and the messages could be confusing, as it could show "New item added. 4 items in array", "New item added. 4 items in array", "Item removed. 4 items in array", due to the add/remove happening before the prints.

So for proper functionality for the given code (or similar), you would have to synchronize the methods or use an explicit shared lock. There is a possibility of sending the commands in the wrong order, but in the given example (provided the list is made thread-safe) there's no chance of real danger, as it would only result in a duplicated set for the same data.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=385017&siteId=1