Threadsafe quadtree without locking

I am currently preparing some trainingpapers about multithreading and I thought some things might be of interest for you.

For pretty long the only possible way for save multithreading in java was to do locking using either synchronized or a mutex/semaphore… These techniques are quite heavy weight and nearly unusable for treestructures when you want to have locks for each node.

A long time ago some wise guys invented the compareandset operations which are atomically executed. Since jdk 5 the java.util.atomic package provides versions of the primitive dataypes that support this operation.

Compare and set means that within one operation the cpu checks a value and if the value has the expected value sets it to a new one.

To showcase what you can do I wrote up a little quadtree using this functionality.

Some notes:

  • cas only makes sense on a MULTICORE or MULTIPROCESSOR system as it is aimed at systems with several processors (on a singlecore system cas will actually reduce the efficiency of your system).
  • cas sometimes uses something that probably got taught to you as a nono but due to the nature of the algorithm and the multiple cores is a big yesyes: SPINLOCKS
  • the quadtree does breadth first search to allow multiple threads to work on the structure
  • the code might contain bugs, I will be able to look over it later this week (this is my first cas based quadtree, i only used cas for some dataproccessing in the past)
  • cas is dangerous as you have to watch more closely what you lock and what you release, there is no help from java for that stuff as is for standard locks



    This nodeclass allows several threads to manipulate/create the structure.


package de.pflanzenmoerder.trees.quadtree;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A node in the quadtree.
 *
 * @author jochen
 *
 */
public class Node {
   /**
    * Bounding volume of this node.
    */
   private BoundingVolume volume;
   /**
    * Contains all the children of this node.
    */
   private Set<QuadLeaf> children;
   /**
    * Contains all the child nodes of this node.
    */
   private Set<Node> childNodes;
   /**
    * Is this node a leaf?
    */
   private boolean leaf;
   /**
    * Minimum size of a node.
    */
   private Float minExtend;
   /**
    * Atomic boolean for cas operations.
    */
   private AtomicBoolean lockBool = new AtomicBoolean(false);

   /**
    * Constructor.
    *
    * @param volume
    *            boundingvolume of this node.
    */
   public Node(BoundingVolume volume, Float minExtend) {
      this.volume = volume;
      this.minExtend = minExtend;
      if (minExtend.equals(volume.getExtend())) {
         leaf = true;
         children = new HashSet<QuadLeaf>();
         return;
      }
      leaf = false;
      childNodes = new HashSet<Node>();
   }

   /**
    * Check if the node is a leaf.
    *
    * @return
    */
   public boolean isLeaf() {
      return leaf;
   }
   
   /**
    * Check if the given leaf intersects the node.
    * @param child
    * @return
    */
   public boolean intersects(QuadLeaf child) {
      return volume.intersects(child.getBoundingVolume());
   }

   /**
    * Add a new child to the node.
    * @param child
    */
   public void addChild(QuadLeaf child) {
      Set<Node> collectedNodes=new HashSet<Node>();
      if (leaf) {
         children.add(child);
         return;
      }
      if (childNodes.size() == 0) {
         Float _extend = volume.getExtend() / 2;
         Node newNode;
         // left top
         newNode = new Node(new BoundingVolume(volume.getX() - _extend,
               volume.getY() - _extend, _extend), minExtend);
         newNode.addChild(child);
         if (newNode.intersects(child)) {
            newNode.lock();
            collectedNodes.add(newNode);
         }
         childNodes.add(newNode);
         // right top
         newNode = new Node(new BoundingVolume(volume.getX() + _extend,
               volume.getY() - _extend, _extend), minExtend);
         newNode.addChild(child);
         if (newNode.intersects(child)) {
            newNode.lock();
            collectedNodes.add(newNode);
         }
         childNodes.add(newNode);
         // left bottom
         newNode = new Node(new BoundingVolume(volume.getX() - _extend,
               volume.getY() + _extend, _extend), minExtend);
         newNode.addChild(child);
         if (newNode.intersects(child)) {
            newNode.lock();
            collectedNodes.add(newNode);
         }
         childNodes.add(newNode);
         // right bottom
         newNode = new Node(new BoundingVolume(volume.getX() + _extend,
               volume.getY() + _extend, _extend), minExtend);
         newNode.addChild(child);
         if (newNode.intersects(child)) {
            newNode.lock();
            collectedNodes.add(newNode);
         }
         childNodes.add(newNode);
      }
      if (collectedNodes.size() == 0) {
         //check what nodes the child intersects
         for (Node node : childNodes) {
            if (node.intersects(child)) {
               collectedNodes.add(node);
               do{
                  //spinlock!!! :)
               }while(!node.lock());
            }
         }
      }
      //we are done processing, unlock
      unlock();
      for (Node node : collectedNodes) {
         node.addChild(child);
      }
   }

   /**
    * Used to perform a cas for locking this node.
    *
    * @return false if the lock failed.
    */
   public boolean lock() {
      return lockBool.compareAndSet(false, true);
   }

   /**
    * Used to perform a cas for unlocking this node.
    *
    * @return false if the unlock failed.
    */
   public boolean unlock() {
      return lockBool.compareAndSet(true, false);
   }
}



If there is interest I will post another class that shows how to do multithreaded collisiondetection when I am done with it.

Watch out for atomics - they are not guaranteed to work in a jvm, more so when you multithread.



You could of course delegate quadtree checks in a seperate thread

Atomics are guaranteed to work, that's the purpose of the whole atomic package they created.

What is not guaranteed is that they are nonblocking which depends on the underlying processor (older processors don't have cas). All processors of the last 2-3 years definitely support cas and most from the years before also do.


This sounds great, now all I need is a situation to test it in and some time :slight_smile: Thank you for contributing this pflanzenmoerder!

pflanzenmoerder said:

Atomics are guaranteed to work, that's the purpose of the whole atomic package they created.
What is not guaranteed is that they are nonblocking which depends on the underlying processor (older processors don't have cas). All processors of the last 2-3 years definitely support cas and most from the years before also do.




Which means they are not guaranteed..

Edit - although its better than volatile and synchronized. Thing with processors of last 2 - 3 years means that they are still being sold now in lower spec machines

They are guaranteed to work they just don't give you the speedup you would expect as the JVM internally uses a synchronized block on architectures that don't support cas. Not working would imply that your code breaks which it doesn't.

Also, cas is aimed at multicore and multiprocessor systems and all of those support cas operations.

I also researched a little more and 2-3 years is not true, it is more to 5 years.

Anyways, to really utilize more than one core in an efficient way cas operations are the way to go as the use of locks (in structures like quadtrees,…) comes at either a big cost (sending a thread into sleep isn't a very lightweight operation) or the loss of granularity.


Big Applaud



Think you are correct now in your implementation with atomics, last time I visited this was several years ago, but from what I can find in research and tests this is the right way to go.



Have some quad xeons, at the weekend will test these to ensure they are cas friendly. Thanks pflanz for the code…

what is "cas"?

CAS stands for "compare and set".

Example:

You have a variable:

AtomicBoolean lock=ne AtomicBoolean(true);



Then you do:

lock.compareAndSet(true,false)



What happens is that you check if the vars current value is true, if it is you change it to false.

That itself isn't the big deal. The big deal is that this operation is atomic (happens in one step and can't be interrupted by the os-scheduler).

theprism said:

Have some quad xeons, at the weekend will test these to ensure they are cas friendly. Thanks pflanz for the code.....

Pffff, and I have to play with my crappy little dualcore here  :(
Hehe, but on a quadcore this should be even more fun.

I am more than interested in reading some of those trainingpapers you are writing on Multithreading if you are willing to share them.

This purely out of an educational viewpoint, not an editorial viewpoint, because I think I could learn a great deal from them. :slight_smile:

pflanzenmoerder said:

theprism said:

Have some quad xeons, at the weekend will test these to ensure they are cas friendly. Thanks pflanz for the code.....

Pffff, and I have to play with my crappy little dualcore here  :(
Hehe, but on a quadcore this should be even more fun.


Search ebay for compaq proliant 6400r - Old quad xeons 550 mhz, but would only cost you 50 euros ( was 20,000 new )

@Methius: I will keep posting the code and the explanations for it. The trainingpapers will follow (right now it's just 2 whiteboards filled with everything). Just monitor this thread.



@theprism: Wow, that's pretty cool. But if everything goes well I will do a little sidejob for a company of a friend and they are doing livestreaming on quadcores  :smiley:

I will do that, thank you for taking the time in presenting the code and even further explaining it. :slight_smile: