Unmapping MappedByteBuffer

Zhong Yu zhong.j.yu at gmail.com
Fri Nov 2 19:59:39 PDT 2012


(cc Doug: a non-trivial use case for ReentrantLock.isHeldByCurrentThread())

On Fri, Nov 2, 2012 at 5:24 AM, Alan Bateman <Alan.Bateman at oracle.com> wrote:
> On 02/11/2012 01:29, Jeff Hain wrote:
>
> :
>
>   That's modulo the fact that MappedByteBuffers can't be
> explicitly and portably unmapped (Bug ID: 4724038), so
> you can't always use them.
>
> Yes, a very difficult issue that many people have looked at but didn't come
> up a solution that addresses all the concerns, see:
>
> http://bugs.sun.com/view_bug.do?bug_id=4724038

(I just saw it questioned on SO: http://stackoverflow.com/questions/13204656 )

I think we can solve the problem by enforcing that any thread
accessing the buffer must "own" it first, and there can only be one
owner:

        bb.own();

        bb.get(0);
        ....
        bb.get(n);

        bb.disown();

The access methods check first that the current thread is the owner
thread. This check can be very cheap, e.g.
ReentrantLock.isHeldByCurrentThread().

After explicit disposal, no thread can obtain the ownership anymore,
therefore no thread can access the buffer anymore.

I tested this strategy (source attached below), for tight loops of
get(index), the speed is 90% of bare ByteBuffer.get(index). Ideally,
VM optimization should be able to completely remove the check of
ownership, making the 2 versions equally fast.


Zhong Yu

https://gist.github.com/4005639

SingleOwnerByteBuffer.java
=========================================

import java.nio.ByteBuffer;
import java.util.concurrent.locks.ReentrantLock;

public class SingleOwnerByteBuffer
{
    final ReentrantLock lock = new ReentrantLock();

    ByteBuffer bb;
    boolean disposed;

    public SingleOwnerByteBuffer(ByteBuffer bb)
    {
        this.bb = bb;
    }

    public int length()
    {
        assertOwnedByCurrentThread();

        return bb.remaining();
    }
    public byte get(int index)
    {
        assertOwnedByCurrentThread();

        return bb.get(index);
    }

    public void dispose()
    {
        lock.lock();
        try
        {
            if(disposed)
                return;

            /* unmap or dealloc bb here */

            disposed = true;
        }
        finally
        {
            lock.unlock();
        }
    }

    public void own()
    {
        lock.lock();

        if(disposed)
        {
            lock.unlock();
            throw new IllegalStateException("disposed");
        }
    }

    void assertOwnedByCurrentThread()
    {
        if(!lock.isHeldByCurrentThread())
            throw new IllegalStateException("not owned by current thread");
    }

    public void disown()
    {
        lock.unlock();
    }

    public static void main(String[] args) throws Exception
    {
        //test1(100_000, 1000_000);
        test2(100_000, 1000_000);
    }

    static public void test1(int cap, int repeat) throws Exception
    {
        int sum=0;
        long time = System.currentTimeMillis();
        ByteBuffer bb = ByteBuffer.allocateDirect(cap);
        for(int r=0; r<repeat; r++)
        {
            for(int i=0; i<cap; i++)
            {
                sum += bb.get(i);
            }
        }
        time = System.currentTimeMillis() - time;
        System.out.printf("time=%,d, sum=%,d %n", time, sum);
    }
    static public void test2(int cap, int repeat) throws Exception
    {
        int sum=0;
        long time = System.currentTimeMillis();
        ByteBuffer bb0 = ByteBuffer.allocateDirect(cap);
        SingleOwnerByteBuffer bb = new SingleOwnerByteBuffer(bb0);
        bb.own();
        for(int r=0; r<repeat; r++)
        {
            for(int i=0; i<cap; i++)
            {
                sum += bb.get(i);
            }
        }
        bb.disown();
        time = System.currentTimeMillis() - time;
        System.out.printf("time=%,d, sum=%,d %n", time, sum);
    }
}


More information about the nio-dev mailing list