private static void start(final int num_threads, final int num_msgs, boolean oob, int max_msg_batch_size) {
        final UNICAST unicast=new UNICAST();
        final AtomicInteger counter=new AtomicInteger(num_msgs);
        final AtomicLong seqno=new AtomicLong(1);
        final AtomicInteger delivered_msgs=new AtomicInteger(0);
        final Lock lock=new ReentrantLock();
        final Condition all_msgs_delivered=lock.newCondition();
        final ConcurrentLinkedQueue<Long> delivered_msg_list=new ConcurrentLinkedQueue<Long>();
        final Address local_addr=Util.createRandomAddress("A");
        final Address sender=Util.createRandomAddress("B");
        unicast.setDownProtocol(new Protocol() {
            public Object down(Event evt) {
                return null;
            }
        });
        unicast.setUpProtocol(new Protocol() {
            public Object up(Event evt) {
                if(evt.getType() == Event.MSG) {
                    delivered_msgs.incrementAndGet();
                    UNICAST.UnicastHeader hdr=(UNICAST.UnicastHeader)((Message)evt.getArg()).getHeader(UNICAST_ID);
                    if(hdr != null)
                        delivered_msg_list.add(hdr.getSeqno());
                    if(delivered_msgs.get() >= num_msgs) {
                        lock.lock();
                        try {
                            all_msgs_delivered.signalAll();
                        }
                        finally {
                            lock.unlock();
                        }
                    }
                }
                return null;
            }
        });
        unicast.down(new Event(Event.SET_LOCAL_ADDRESS, local_addr));
        unicast.setMaxMessageBatchSize(max_msg_batch_size);
        // send the first message manually, to initialize the AckReceiverWindow tables
        Message msg=createMessage(local_addr, sender, 1L, oob, true);
        unicast.up(new Event(Event.MSG, msg));
        Util.sleep(500);
        final CountDownLatch latch=new CountDownLatch(1);
        Adder[] adders=new Adder[num_threads];
        for(int i=0; i < adders.length; i++) {
            adders[i]=new Adder(unicast, latch, counter, seqno, oob, local_addr, sender);
            adders[i].start();
        }
        long start=System.currentTimeMillis();
        latch.countDown(); // starts all adders
        lock.lock();
        try {
            while(delivered_msgs.get() < num_msgs) {
                try {
                    all_msgs_delivered.await(1000, TimeUnit.MILLISECONDS);
                    System.out.println("received " + delivered_msgs.get() + " msgs");
                    
                    // send a spurious message to trigger removal of pending messages in AckReceiverWindow
                    msg=createMessage(local_addr, sender, 1L, oob, false);
                    unicast.up(new Event(Event.MSG, msg));
                }
                catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        finally {
            lock.unlock();
        }
        long time=System.currentTimeMillis() - start;
        double requests_sec=num_msgs / (time / 1000.0);
        System.out.println("\nTime: " + time + " ms, " + Util.format(requests_sec) + " requests / sec\n");