Archer
Archer

Reputation: 5147

Spring scope=`prototype`: how to change all bean instances?

I've a simple class which perform some check agains list of Regex patterns. My patterns are set somehow with constant list of values. This is not a question as for now, so I'm omitting that setting code.

class Checker {
   private final Pattern [] patterns = new Pattern[10];

   public boolean check(final String param){
     for(Pattern p : patterns){
        if(p.matcher(param).matches())
           return true;
      }
     return false;
   }
} 

Stuff is failry simple. Now I'm accessing that class from 500 simultaenous threads. Class is created with default Spring scope singleton.

Making access to patterns synchronous:

class Checker {
   private final Pattern [] patterns = new Pattern[10];

   public boolean check(final String param){
     synchronized(patterns){
      for(Pattern p : patterns){
         if(p.matcher(param).matches())
            return true;
      }
     return false;
     }
   }
} 

Well, since all threads access single instance of my class - they gets locked within synchronized block. Since my class is fairly lightweight I'd like to create separate instance for each thread (e.g. change scope from singleton to prototype). Removing synchronization block and adding getInstance factory method backed by ThreadLocal. Like that:

class Checker {
   private final Pattern [] patterns = new Pattern[10];

   public boolean check(final String param){
     for(Pattern p : patterns){
        if(p.matcher(param).matches())
           return true;
      }
     return false;
   }

   private static final ThreadLocal<Checker> checkerLocal =
      new ThreadLocal<Checker>() {
       @Override 
       protected Checker initialValue() {
         System.out.println("Creating Checker instance")
         return new Checker();
       }
   };

   public static Checker getInstance(){
          return checkerLocal.get();  
   }
}

Works like a charm.

However, imagine that I've to dynamically modify patterns somehow inside my application (e.g. add or remove Pattern there). I should perform that for all existing instances of Checker class. I'm thinking on using ApplicationContext#getBeansOfType(Checker.class) method to get all bean instances. But is there any other more springy way to do so?

Upvotes: 0

Views: 1153

Answers (1)

Here's my suggestion. The Checker class doesn't have to have any synchronization at all, since it only ever pulls an unmodifiable List<Pattern> out of the holder class, and the only synchronization needed is to prevent clashing updates on the holder.

public class PatternService {
    AtomicReference<List<Pattern>> patterns = new AtomicReference<>(Collections.emptyList());

    public List<Pattern> getPatterns() {
        return patterns.get();
    }

    public synchronized void addPattern(Pattern p) {
        List<Pattern> newPatterns = new LinkedList<>(patterns.get());
        newPatterns.add(p);
        patterns.set(Collections.unmodifiableList(newPatterns)); // or ImmutableList
    }

    // other mutators
}


public class Checker {
    @Autowired PatternService patternService;

    public boolean check(String param) {
        for(Pattern p: patternService.getPatterns())
            if(p.matcher(param).matches())
                return true;

        return false;
    }
}

Upvotes: 2

Related Questions