Java线程安全锁定

时间:2016-04-12 12:39:59

标签: java multithreading thread-safety locking synchronized

我正在修改Java服务器软件。整个应用程序是单线程的。我的一个更改需要花费很多时间,所以我决定异步执行以避免冻结主线程。

这是原始代码的示例(不是真实代码,只是一个示例):

public class Packet {
    private final byte[] data = new byte[1024];

    public void setData(int index, byte data) {
        this.data[index] = data;
    }

    public byte getData(int index) {
        return data[index];
    }

    public void sendPacket(ClientConnection clientConnection) {
        clientConnection.sendPacket(data);
    }
}

目前这是我的代码(查看评论):

public class Packet {
    private final byte[] data = new byte[1024];

    public void setData(int index, byte data) {
        synchronized (this) {
            this.data[index] = data;
        }
    }

    public byte getData(int index) {
        return data[index];
    }

    public void sendPacket(final ClientConnection clientConnection) {
        //This state of data should be sent
        new Thread(new Runnable() {
            @Override
            public void run() {
                //The thread is now running
                //The main-thread can move on
                //The main-thread can also modify data now because we are not inside the synchronized block
                //But it should not because the state of data when the method sendPacket was called should be sent
                synchronized (Packet.this) {
                    thisTakesMuchTime(data);
                    clientConnection.sendPacket(data);
                }
            }
        }).start();
    }
}

我实际上正在寻找的是:

public class Packet {
    private final byte[] data = new byte[1024];

    public void setData(int index, byte data) {
        //wait for unlock
        this.data[index] = data;
    }

    public byte getData(int index) {
        return data[index];
    }

    public void sendPacket(final ClientConnection clientConnection) {
        //lock
        new Thread(new Runnable() {
            @Override
            public void run() {
                thisTakesMuchTime(data);
                clientConnection.sendPacket(data);
                //unlock
            }
        }).start();
    }
}

问题:Java中这种锁的最佳实现是什么?我应该自己使用AtomicInteger来做这件事。

编辑:查看我当前实施的答案。

4 个答案:

答案 0 :(得分:2)

您可以复制数据并发送副本,以避免并发。

public class Packet {
    private final byte[] data = new byte[1024];

    public void setData(final int index, final byte data) {
        this.data[index] = data;
    }

    public byte getData(final int index) {
        return data[index];
    }

    public void sendPacket(final ClientConnection clientConnection) {
        byte[] dataToSend = new byte[1024];
        System.arraycopy(data, 0, dataToSend, 0, 1024);
        new Thread(new Runnable() {
            @Override public void run() {
                clientConnection.sendPacket(dataToSend);
            }
        }).start();
    }
}

使用CopyOnWriteArrayList类似于下面的代码,这也避免了并发性,但效率不高(假设您setDatasendPacket更频繁地调用public class Packet { private byte[] data = new byte[1024]; public void setData(final int index, final byte data) { byte[] newData = new byte[1024]; System.arraycopy(data, 0, newData, 0, 1024); newData[index] = data; this.data = newData; } public byte getData(final int index) { return data[index]; } public void sendPacket(final ClientConnection clientConnection) { new Thread(new Runnable() { @Override public void run() { clientConnection.sendPacket(data); } }).start(); } } :< / p>

{{1}}

答案 1 :(得分:1)

您可以使用的最简单的锁是Reentrant Lock,可重入意味着,如果您在拥有锁时尝试获取锁,则操作将成功。

在您的代码中,为了实现您想要的线程,您还必须使用wait()notify()来阻止主线程,直到您的子线程获得锁定为止:

public class Packet {
    private final ReentrantLock lock = new ReentrantLock();
    private final byte[] data = new byte[1024];

    public void setData(int index, byte data) {
        lock.lock(); //wait for unlock
        try {
            this.data[index] = data;
        } finally {
            lock.unlock();
        }
    }

    public byte getData(int index) {
        return data[index];
    }

    public void sendPacket(final ClientConnection clientConnection) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock(); //lock
                try {
                    synchronized(this) {
                        this.notify();
                    }

                    thisTakesMuchTime(data);
                    clientConnection.sendPacket(data);
                } finally { 
                    lock.unlock(); //unlock
                }
            }
        }).start();

        synchronized(thread) {
            try {
                thread.wait();
            } catch (InterruptedException e) {
                //handle
            }
        }
    }
}

还要考虑使用ExecutorService而不是创建原始Thread对象。

答案 2 :(得分:0)

  

Java中这种锁的最佳实现是什么?我应该自己使用AtomicInteger来做这件事。

我认为@ ericbn的答案可行。使用主线程但仍然在Packet内部抓取数据副本很好。

然而你担心1k缓冲区?这里的真实费用不是在主线程中创建数据的副本,而是每次分支线程分支发送数据包。与对象创建相比,这非常昂贵。我使用线程池并向其提交数据包作业。

// you might want this to be bounded so you don't queue up too many packets
private final ExecutorService threadPool = Executors.newSingleThreadExecutor();
...

public void sendPacket(ClientConnection clientConnection) {
    byte[] dataToWrite = new byte[data.length];
    System.arraycopy(data, 0, dataToWrite, 0, dataToWrite.length);
    threadPool.submit(new PacketToWrite(dataToWrite, clientConnection));
    // you can clear or reset the `data` array now
}

private static class PacketToWrite implements Runnable {
    private final byte[] dataToWrite;
    private final ClientConnection clientConnection;
    public PacketToWrite(byte[] dataToWrite, ClientConnection clientConnection) {
        this.dataToWrite = dataToWrite;
        this.clientConnection = clientConnection;
    }
    public void run() {
        thisTakesMuchTime(data);
        clientConnection.sendPacket(data);
    }
}

您正在通过网络发送数据,因此与网络延迟相比,额外的对象带宽无效。

答案 3 :(得分:0)

我目前的实施:

分组:

public class Packet {
    private final Lock lock = new Lock();
    private final byte[] data = new byte[1024];

    public void setData(int index, byte data) {
        lock.waitUntilUnlock();
        this.data[index] = data;
    }

    public byte getData(int index) {
        return data[index];
    }

    public void sendPacket(final ClientConnection clientConnection) {
        lock.lock();
        new Thread(new Runnable() { // I use an ExecutorService
            @Override
                public void run() {
                thisTakesMuchTime(data);
                clientConnection.sendPacket(data);
                lock.unlock();
            }
        }).start();
    }
}

锁定:

public class Lock {
    private final AtomicInteger lockCount = new AtomicInteger();

    public void lock() { // Main thread
        lockCount.incrementAndGet();
    }

    public synchronized void unlock() {
        lockCount.decrementAndGet();
        notifyAll();
    }

    public synchronized void waitUntilUnlock() { // Main thread
        try {
            while (lockCount.get() > 0) {
                wait();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}