【问题标题】:Java: Serialize object to file asynchronouslyJava:异步序列化对象到文件
【发布时间】:2019-03-20 16:41:38
【问题描述】:

我正在制作一个 Minecraft 克隆,我有一个名为 chunksMap<Vector3i, Chunk> 存储所有加载的块。每一帧,这个循环都会运行:

for(Vector3i v:chunksToUnrender){
    ...
    CompletableFuture.runAsync(() -> {
        try {
            chunks.get(v).toFile(new File(String.format("res-server/regions/%s.%s.%s.mcr", v.x, v.y, v.z)));
            synchronized (chunksStateGuard) {
                chunks.remove(v);
            }
        } catch(IOException e) {
            e.printStackTrace();
            System.err.println("Unable to save chunk " + Utils.format3i(v));
        }
    });
}

这里的目标是异步卸载块。 Chunk.toFile(File)的内容是:

public void toFile(File file) throws IOException {
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(SerializationUtils.serialize(this));
    fos.flush();
    fos.close();
}

然而,尽管使用了CompletableFuture,当一个块被卸载时,游戏会在短时间内触发帧率,因为它会序列化并卸载该块。有什么办法可以避免在后台任务工作时中断主线程?

【问题讨论】:

  • 文件 IO 成本很高,您是否考虑过将常用内容序列化到内存缓存(例如 ehCache 或 Terracota)或从内存缓存中序列化?
  • @RahulR。序列化的重点是减少内存消耗。最初所有的块总是存储在内存中,但是在大约 1m 的游戏过程中,我们会得到延迟峰值和 OutOfMemoryErrors。
  • @RahulR。我不明白你的意思。如果您的建议可以解决问题,请发布答案。

标签: java performance asynchronous serialization completable-future


【解决方案1】:

正如前面 cmets 中所讨论的,您可以通过优化序列化过程同时提供更快读取速度的缓存来进一步增强实现。

以下是一个示例代码,演示了使用 ehCache 设置和使用缓存。

Maven 依赖(在 pom.xml 中)

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.9.0</version>
</dependency>

ehCache 配置 (ehcache.xml)

注意 - 该文件应该在类路径中可用。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
    monitoring="autodetect" dynamicConfig="true">

    <!-- By default, Ehcache stored the cached files in temp folder. -->
    <!-- <diskStore path="java.io.tmpdir" /> -->

    <!-- Ask Ehcache to store cache in this path -->
    <diskStore path="c:\\temp\\ehcache" />

    <!-- Sample cache named cache1
    This cache contains a maximum in memory of 10000 elements, and will expire
    an element if it is idle for more than 5 minutes and lives for more than
    10 minutes.

    If there are more than 10000 elements it will overflow to the
    disk cache, which in this configuration will go to wherever java.io.tmp is
    defined on your system. On a standard Linux system this will be /tmp" -->
    <cache name="ApplicationContentCache" 
        maxEntriesLocalHeap="10000"
        maxEntriesLocalDisk="1000" 
        eternal="false" 
        diskSpoolBufferSizeMB="20"
        timeToIdleSeconds="300" timeToLiveSeconds="600"
        memoryStoreEvictionPolicy="LFU" 
        transactionalMode="off">
        <persistence strategy="localTempSwap" />
    </cache>
</ehcache>

各种 Java 类

import java.io.Serializable;
import java.util.concurrent.CompletableFuture;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

/**
 * Demo class for the caching functionality
 * @author Rahul R
 *
 */
public class ApplicationCacheDemo {

    public static void main(String... args) {
        ApplicationCacheFactory cacheFactory = ApplicationCacheFactory.instance;

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try (ApplicationCache<CachableObject> cache = cacheFactory.getApplicationCache()) {
                CachableObject cacheContent = new CachableObject("A sample content");
                int identity = cache.put(cacheContent);

                CachableObject readContent = cache.get(identity);
                System.out.println(readContent.getData());          
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        future.join();
    }
}

/**
 * The class whose objects would be cached.
 * @author Rahul R
 *
 */

class CachableObject implements Serializable {
    private static final long serialVersionUID = 1L;

    private String data = null;

    public CachableObject() {
        super();
    }

    public CachableObject(String data) {
        super();
        setData(data);
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((data == null) ? 0 : data.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        CachableObject other = (CachableObject) obj;
        if (data == null) {
            if (other.data != null)
                return false;
        } else if (!data.equals(other.data))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "CachableObject [" + getData() + "]";
    }
}

/**
 * A singleton factory implemented through Enum
 * @author Rahul R
 *
 */

enum ApplicationCacheFactory {
    instance;

    public ApplicationCache<CachableObject> getApplicationCache() {
        return new ApplicationCache<>("ApplicationContentCache");
    }
}

/**
 * A simplistic cache facade
 * @author Rahul R
 *
 */

class ApplicationCache<E> implements AutoCloseable {    
    private CacheManager cm = null;
    private String cacheName = null;
    private Cache cache = null;

    public ApplicationCache(String cacheName) {
        super();
        setCacheName(cacheName);
        initializeCache();
    }

    private void initializeCache() {
        cm = CacheManager.getInstance();
        cache = cm.getCache(getCacheName());
    }

    public String getCacheName() {
        return cacheName;
    }

    public void setCacheName(String cacheName) {
        this.cacheName = cacheName;
    }

    public int put(E value) {
        int identity = value.hashCode();
        cache.put(new Element(identity, value));
        return identity;
    }

    @SuppressWarnings("unchecked")
    public E get(int identity) {
        E result = null;

        Element element = cache.get(identity);

        if (element != null) {
            result = (E) element.getObjectValue();
        }

        return result;
    }

    @Override
    public void close() throws Exception {
        cm.shutdown();
    }
}

【讨论】:

    【解决方案2】:

    这是一个 X-Y 问题。问题是从文件同步块加载

    【讨论】:

    • 所以删除问题。在这里没有更多用处。
    猜你喜欢
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-29
    • 1970-01-01
    相关资源
    最近更新 更多