0

I need to maintain a list of assignments in a multithreaded environment. The executor service will run serveral threads for few minutes, and each thread can add or remove an assignment from the shared list.

I need to prevent a situation where two or more threads are trying to remove the same assignment.

I'm having truble to decide which collection to use, and how to use it. is there any way to do it without wrapping the list inside a syncronyzed block?

From what i read, CopyOnWritearraylist is suitable mainly for reading. Is Collections.synchronizedList is the answer?

thanks.

public class AssignmentsListComponent {

    private List<Assignment> assignmentsList;
    private boolean isClosed;

    public AssignmentsListComponent(List<Assignment> assignmentsList) {
        super();
        this.assignmentsList = assignmentsList;
        this.isClosed = false;
    }

    public void addAssignment(Assignment assignment) throws ClosedAssignmentsListException{
        if(this.isClosed)
            throw new ClosedAssignmentsListException("Cannot add Assignment. List is closed");
        this.assignmentsList.add(assignment);
    }

    public void removeAssignment()throws ClosedAssignmentsListException{
        if(this.isClosed)
            throw new ClosedAssignmentsListException("Cannot add Assignment. List is closed");
        Assignment assignmentToRemove = this.assignmentsList.get(new Random().nextInt(this.assignmentsList.size()-1));
        this.assignmentsList.remove(assignmentToRemove);
        this.isClosed = this.assignmentsList.isEmpty();
    }


}


public class SchedulerService {

    private final static long PERIOD = 1500;//Repeat interval
    private final static long TIME_TO_RUN = 5;

    private Map <Family, AssignmentsListComponent> familyAssignmentsListMap;

    private void initializeFamilyAssignmentsListMap(){
        List<Family> families = HibernateUtil.getAllFamilies();
        for(Family family :families){
            List<Assignment> assignmentsList = new ArrayList<Assignment>();
            AssignmentsListComponent assignmentsListComponent = new AssignmentsListComponent(assignmentsList);
            this.familyAssignmentsListMap.put(family, assignmentsListComponent);
        }

    }

    public void runFamilyMemberThreds(){

        List<Assignment> allAssignments = HibernateUtil.getAllAssignments();
        List<FamilyMember> familyMembers = HibernateUtil.getAllFamilyMembers();
        // create executor to run the threads for all family members
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(familyMembers.size());

        /**
         * For each of the family members, run in PERIOD intervals, and kill it's task after TIME_TO_RUN in minutes 
         */
        for(FamilyMember familyMember :familyMembers){
            // Execute task every 30 seconds
            final ScheduledFuture<?> handler = executorService.scheduleAtFixedRate(new FamilyMemberThred(familyMember, allAssignments,  familyAssignmentsListMap.get(familyMember.getFamily())),0,PERIOD,TimeUnit.MILLISECONDS);
            Runnable cancel = new Runnable()
            {
                public void run()
                {
                    handler.cancel(true);
                }
            };
            executorService.schedule(cancel,TIME_TO_RUN,TimeUnit.MINUTES);//Cancels the task after 5 minutes
            executorService.shutdown();
        }
    }
}


public class FamilyMemberThred implements Runnable{

        private List<Assignment> allAssignments;
        private FamilyMember familyMember;
        private AssignmentsListComponent assignmentsListComponent;

        public void run() {
            if(isAddAssignment())
                addAssignment();
            else 
                removeAssignment();
        }

        private void addAssignment(){
            Assignment randomAssignmentToAdd = this.allAssignments.get(new Random().nextInt(allAssignments.size()-1));
            try {
                this.assignmentsListComponent.addAssignment(randomAssignmentToAdd);
            } catch (Exception e) {
                logClosedListException(randomAssignmentToAdd);
            }
        }

        private void removeAssignment(){
            try {
                this.assignmentsListComponent.removeAssignment();
            } catch (Exception e) {
                logClosedListException(null);
            }
        }

        private void logClosedListException(Assignment assignment){
            Log log = new Log(familyMember, Action.ADD, assignment, Boolean.FALSE);
            HibernateUtil.saveLog(log);
        }


        /**
         * possible values between 0-2, so if 1 or 2, return true
         */
        private boolean isAddAssignment(){
            return new Random().nextInt(3) > 0;  
        }

    }
  • Do you actually need the List methods, like get(index)? If not, this sounds like a perfect use case for BlockingQueue. – yshavit Oct 14 '16 at 00:42
  • i dont, but i dont see why is it a perfect use case, since i dont have limitation for the number of assignments, and once the list is cleared, i close it, and dont wait for the next adding. – user3845295 Oct 14 '16 at 21:39
  • The threads that are putting values in call `put`, the threads that are taking them out call `take`, and the synchronization is handled for you by the implementation. (There are other methods than put/take, which have different semantics: see https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/BlockingQueue.html) – yshavit Oct 14 '16 at 21:41
  • ok, you convinced me :-) thanks a lot. – user3845295 Oct 14 '16 at 22:42
  • only one more question if i may. if the AssignmentsListComponent is not syncronyzed then the boolean value of isClosed may not be concurrent. how do i make sure that other threads that share the queue are stopping their execution? – user3845295 Oct 14 '16 at 23:14
  • Just add **synchronized** keyword on each method of AssignmentsListComponent. This will solve your problem. – Slava Oct 14 '16 at 23:47
  • what about defining the boolean value as volatile as mentioned here? http://stackoverflow.com/questions/812342/how-to-interrupt-a-blockingqueue-which-is-blocking-on-take – user3845295 Oct 15 '16 at 00:35
  • Removal from the synchronized list/blocking queue and modifying the volatile variable will not be atomic operation, right it contains two operations that each of them is atomic, but as whole one operation, it is NOT atomic. What you want is to make the whole operation to be atomic. The easiest way to achieve that is to put synchronized on each public method of AssignmentsListComponent. – Slava Oct 15 '16 at 03:03
  • so is it right to say that the only adavantage of using collections provided by the concurrrent api is for atomic actions? – user3845295 Oct 15 '16 at 09:12

2 Answers2

0

Probably, the simplest way to synchronize is to add synchronized keyword for each method of AssignmentsListComponent class. This will make all the methods synchronized, in other word, their execution will be mutable exclusive.

The AssignmentsListComponent's assignmentsList field is passed as a parameter to the constructor. Potentially, the caller may modify the list (it does not in this code snippet but can do that).

Possible solutions for the last issue:

  • to create an empty list inside the constructor and assign it to the field. Of course, the caller will not be able to pass the list into the constructor. Which is probably fine. If not, then...
  • to copy the parameter, and assign the copy to the field assignmentsList.
Slava
  • 827
  • 5
  • 13
  • The whole idea is that each family has its own list. if i will make a copy of the list for each member, then it wouldn't be a shared list. – user3845295 Oct 14 '16 at 21:45
  • You share AssignmentsListComponent instance between threads. No point to share the list here. AssignmentsListComponent instance will synchronize the access from different threads. In this implementation, the **synchronize** keyword attached for each public method of AssignmentsListComponent class will do the "magic". – Slava Oct 14 '16 at 23:43
0

CopyOnWriteArrayList is good if you don't plan on having a lot of insertions since it can be inefficient. You seem mostly concerned with removals while another thread is iterating so it should be ok. From another post, the concurrent classes are preferred. You can read here: Difference between CopyOnWriteArrayList and synchronizedList

Community
  • 1
  • 1
Seephor
  • 1,692
  • 3
  • 28
  • 50