public void unregister(PeerControlInstance instance) {
    try {
      this_mon.enter();

      Map<PeerControlInstance, instanceWrapper> new_map =
          new HashMap<PeerControlInstance, instanceWrapper>(instance_map);

      instanceWrapper wrapper = new_map.remove(instance);

      if (wrapper == null) {

        Debug.out("instance wrapper not found");

        return;
      }

      wrapper.unregister();

      instance_map = new_map;

      registrations_changed = true;

    } finally {

      this_mon.exit();
    }
  }
  public void register(PeerControlInstance instance) {
    instanceWrapper wrapper = new instanceWrapper(instance);

    wrapper.setNextTick(latest_time + random.nextInt(SCHEDULE_PERIOD_MILLIS));

    try {
      this_mon.enter();

      Map<PeerControlInstance, instanceWrapper> new_map =
          new HashMap<PeerControlInstance, instanceWrapper>(instance_map);

      new_map.put(instance, wrapper);

      instance_map = new_map;

      pending_registrations.add(wrapper);

      registrations_changed = true;

    } finally {

      this_mon.exit();
    }
  }
  protected void schedule() {
    SystemTime.registerMonotonousConsumer(
        new SystemTime.TickConsumer() {
          public void consume(long time) {
            synchronized (PeerControlSchedulerBasic.this) {
              PeerControlSchedulerBasic.this.notify();
            }
          }
        });

    List<instanceWrapper> instances = new LinkedList<instanceWrapper>();

    long tick_count = 0;
    long last_stats_time = SystemTime.getMonotonousTime();

    while (true) {

      if (registrations_changed) {

        try {
          this_mon.enter();

          Iterator<instanceWrapper> it = instances.iterator();

          while (it.hasNext()) {

            if (it.next().isUnregistered()) {

              it.remove();
            }
          }

          for (int i = 0; i < pending_registrations.size(); i++) {

            instances.add(pending_registrations.get(i));
          }

          pending_registrations.clear();

          registrations_changed = false;

        } finally {

          this_mon.exit();
        }
      }

      latest_time = SystemTime.getMonotonousTime();

      long current_schedule_count = schedule_count;

      for (instanceWrapper inst : instances) {

        long target = inst.getNextTick();

        long diff = latest_time - target;

        if (diff >= 0) {

          tick_count++;

          inst.schedule(latest_time);

          schedule_count++;

          long new_target = target + SCHEDULE_PERIOD_MILLIS;

          if (new_target <= latest_time) {

            new_target = latest_time + (target % SCHEDULE_PERIOD_MILLIS);
          }

          inst.setNextTick(new_target);
        }
      }

      synchronized (this) {
        if (current_schedule_count == schedule_count) {

          wait_count++;

          try {
            long wait_start = SystemTime.getHighPrecisionCounter();

            wait(SCHEDULE_PERIOD_MILLIS);

            long wait_time = SystemTime.getHighPrecisionCounter() - wait_start;

            total_wait_time += wait_time;

          } catch (Throwable e) {

            Debug.printStackTrace(e);
          }

        } else {

          yield_count++;

          Thread.yield();
        }
      }

      long stats_diff = latest_time - last_stats_time;

      if (stats_diff > 10000) {

        // System.out.println( "stats: time = " + stats_diff + ", ticks = " + tick_count + ", inst =
        // " + instances.size());

        last_stats_time = latest_time;

        tick_count = 0;
      }
    }
  }
  protected void schedule() {
    latest_time = SystemTime.getMonotonousTime();
    SystemTime.registerMonotonousConsumer(
        new SystemTime.TickConsumer() {
          public void consume(long time) {
            synchronized (PeerControlSchedulerPrioritised.this) {
              latest_time = time;
              PeerControlSchedulerPrioritised.this.notify();
            }
          }
        });

    ArrayList instances = new ArrayList();

    long latest_time_used = 0;
    int scheduledNext = 0;
    long currentScheduleStart = latest_time;
    long last_stats_time = latest_time;

    while (true) {
      if (registrations_changed) {
        try {
          this_mon.enter();
          Iterator it = instances.iterator();
          while (it.hasNext()) {
            if (((instanceWrapper) it.next()).isUnregistered()) {
              it.remove();
            }
          }

          for (int i = 0; i < pending_registrations.size(); i++)
            instances.add(pending_registrations.get(i));

          pending_registrations.clear();

          // order instances by their priority (lowest number first)
          Collections.sort(instances);

          if (instances.size() > 0) {
            for (int i = 0; i < instances.size(); i++)
              ((instanceWrapper) instances.get(i))
                  .setScheduleOffset((SCHEDULE_PERIOD_MILLIS * i) / instances.size());
          }

          scheduledNext = 0;
          currentScheduleStart = latest_time;

          registrations_changed = false;
        } finally {
          this_mon.exit();
        }
      }

      tokenDispenser.update(latest_time);

      for (int i = scheduledNext; i < instances.size(); i++) {
        instanceWrapper inst = (instanceWrapper) instances.get(i);
        if (currentScheduleStart + inst.getScheduleOffset() > latest_time_used)
          break; // too early for next task, continue waiting
        if (i == 0 || !useWeights) tokenDispenser.refill();
        // System.out.println("scheduling "+i+" time:"+latest_time);
        inst.schedule();
        schedule_count++;
        scheduledNext++;
        if (scheduledNext >= instances.size()) {
          scheduledNext = 0;
          // try to run every task every SCHEDULE_PERIOD_MILLIS on average
          currentScheduleStart += SCHEDULE_PERIOD_MILLIS;
          // if tasks hog too much time then delay to prevent massive
          // catch-up-hammering
          if (latest_time_used - currentScheduleStart > SCHEDULE_PERIOD_MAX_CATCHUP)
            currentScheduleStart = latest_time_used + SCHEDULE_PERIOD_MILLIS;
        }
      }

      /*
      for (Iterator it=instances.iterator();it.hasNext();){
      	instanceWrapper	inst = (instanceWrapper)it.next();
      	long	target = inst.getNextTick();
      	long	diff = target - latest_time_used;

      	if ( diff <= 0 || diff > SCHEDULE_PERIOD_MILLIS ){
      		inst.schedule();
      		long new_target = target + SCHEDULE_PERIOD_MILLIS;
      		diff = new_target - latest_time_used;
      		if ( diff <= 0 || diff > SCHEDULE_PERIOD_MILLIS )
      			new_target = latest_time_used + SCHEDULE_PERIOD_MILLIS;
      		inst.setNextTick( new_target );
      	}
      }*/

      synchronized (this) {
        if (latest_time == latest_time_used) {
          wait_count++;
          try {
            long wait_start = SystemTime.getHighPrecisionCounter();
            wait();
            long wait_time = SystemTime.getHighPrecisionCounter() - wait_start;
            total_wait_time += wait_time;
          } catch (Throwable e) {
            Debug.printStackTrace(e);
          }

        } else {
          yield_count++;
          Thread.yield();
        }

        latest_time_used = latest_time;
      }

      long stats_diff = latest_time_used - last_stats_time;

      if (stats_diff > 10000) {
        // System.out.println( "stats: time = " + stats_diff + ", ticks = " + tick_count + ", inst =
        // " + instances.size());
        last_stats_time = latest_time_used;
      }
    }
  }