【问题标题】:Java ArrayList Multi-Threading NullPointerExceptionJava ArrayList 多线程 NullPointerException
【发布时间】:2016-05-31 05:19:15
【问题描述】:

我在 Java 中创建了 Glassfish 4.0 服务器,但在我的 ArrayList<> 上遇到了一个奇怪的错误。这是我的init() 代码:

@Override
public void contextInitialized(ServletContextEvent arg0) {
    init();
}

private void init() {
    sessions = new ArrayList<Session>();

    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized(sessions){
                while(sessions.size() == 0){
                    try {
                        Thread.sleep(1000);
                        System.out.println("No sessions available: " + sessions);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    }).start();
}

本质上,当服务器启动时,会话ArrayList 被实例化,一个新的线程开始循环等待 WebSocket 连接发生。下面是 WebSocket 连接的代码:

@OnOpen
public void open(Session session) {
    System.out.println("CONNECTED: " + session.getId());
    synchronized(sessions){
        sessions.add(session);
    }
}

因此,带有 while 循环的线程应该打印出“没有可用的会话:[]”,直到调用 open(...) 方法。然而,只要 open 方法中的代码到达synchronized(sessions) {...},它就会说会话为空。考虑到在创建会话 ArrayListinit() 方法之前永远不会调用此方法,这没有任何意义。

所以,我之前所做的是添加以下代码:if(sessions == null) sessions = new ArrayList&lt;Session&gt;();,然后同步代码将运行。这将起作用,并且会话将被添加,但它不会出现在第二个线程中。第二个线程仍将循环并打印出没有可用的会话。我错过了什么?

堆栈跟踪:

2016-02-18T15:13:13.601-0500|严重:java.lang.NullPointerException 在 BotManager.open(BotManager.java:134) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:606) 在 org.glassfish.tyrus.core.AnnotatedEndpoint.callMethod(AnnotatedEndpoint.java:431) 在 org.glassfish.tyrus.core.AnnotatedEndpoint.onOpen(AnnotatedEndpoint.java:468) 在 org.glassfish.tyrus.core.EndpointWrapper.onConnect(EndpointWrapper.java:446) 在 org.glassfish.tyrus.server.TyrusEndpoint.onConnect(TyrusEndpoint.java:146) 在 org.glassfish.tyrus.websockets.DefaultWebSocket.onConnect(DefaultWebSocket.java:122) 在 org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler.init(TyrusHttpUpgradeHandler.java:98) 在 org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:777) 在 org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673) 在 com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99) 在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174) 在 org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:357) 在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:260) 在 com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:188) 在 org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:191) 在 org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:168) 在 org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:189) 在 org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119) 在 org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288) 在 org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206) 在 org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136) 在 org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114) 在 org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77) 在 org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838) 在 org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113) 在 org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115) 在 org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55) 在 org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135) 在 org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564) 在 org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544) 在 java.lang.Thread.run(Thread.java:745)

【问题讨论】:

  • sessions 是如何定义的?
  • 全局变量 ArrayList 会话;
  • 能否请您发布您的NullPointerException 的堆栈跟踪?
  • @ScottyPippen 你应该展示整个课程,包括会话成员的声明
  • 您的同步存在死锁问题 - 您的可运行对象会锁定列表,直到其大小大于 0,并且您无法添加到列表中,直到它被解锁...

标签: java multithreading arraylist


【解决方案1】:

在 init() 之前永远不会调用此方法

可能 init() 真正在 open() 之前运行,但状态更改(列表初始化)不一定在 open() 运行的其他线程上可见。会话列表初始化不在任何同步块中,因此它可能对其他线程不可见。您有以下选择:

  • 在类构造函数中(或在字段定义中)创建ArrayList 实例
  • 当您访问或创建会话列表时,始终在this(而不是sessions)上同步(this 永远不会为空)

【讨论】:

  • 同步的(this){ session = new ArrayList();这就是您在 init() 方法中提出的建议吗?
  • 是的。并将每个现有的 syncronized(sessions) 替换为 syncronized(this)
  • 如果我没记错的话,将sessions 设置为volatile should work as well
  • volatile 也可以解决初始化问题,但不能保证 ArrayList 本身的正确行为。此外,同时使用 volatile 和 synchronized 可能会导致性能问题(对此并不完全确定)
  • 我在初始化时仍然得到一个 NullPointer,即使在创建 volatile
【解决方案2】:

您有一个充当ServletContextListener 的类和一个websocket EndPoint

容器创建一个实例,在应用程序启动时充当ServletContextListener,并在客户端连接时创建Endpoint 实例。

现在这些实例中的每一个都有自己的sessions 字段,并且在 Endpoint 中,当您访问它时它仍然为 null(它仅在 ServletContextListener 实例中初始化)。

解决方案:将 sessions 字段设为静态(然后解决死锁问题 - 请参阅 Andrew Williamson 的评论) - 例如,只需使用同步列表。

【讨论】:

  • 完成!这是正确的解决方案!谢谢,我永远不会猜到相同变量有多个底层实例:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-17
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 2013-11-19
相关资源
最近更新 更多