V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ColoThor
V2EX  ›  Java

synchronized 引发的问题,进来解答一下

  •  1
     
  •   ColoThor · 2019-05-29 17:57:04 +08:00 · 1890 次点击
    这是一个创建于 1765 天前的主题,其中的信息可能已经有所发展或是发生改变。

    首先这是一个使用 Netty 框架,并用 UDP 来通信的 Demo。

    主要类如下,代码有点多,可以主要看类 3 与类 5

    1、用于统计并输出客户端数量的 GetDataHandlerAdapter

    public class GetDataHandlerAdapter extends ChannelInboundHandlerAdapter {
    
        private static Logger logger = LogManager.getLogger(GetDataHandlerAdapter.class);
        private static volatile AtomicInteger count = new AtomicInteger(1);
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            logger.info(count.getAndIncrement());
        }
    }
    

    2、服务端 RPCAgentServer

    public class RPCAgentServer {
    
        private GetDataHandlerAdapter getDataHandlerAdapter = new GetDataHandlerAdapter();
    
        public void listen(int port) throws InterruptedException {
            Bootstrap serverBootstrap = new Bootstrap();
            serverBootstrap.handler(new ChannelInitializer<DatagramChannel>() {
                @Override
                public void initChannel(DatagramChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast(getDataHandlerAdapter);
                }
            });
            serverBootstrap.group(new NioEventLoopGroup(1))
                    .channel(NioDatagramChannel.class)
                    .bind(port)
                    .await();
        }
    }
    

    3、客户端 RPCAgentClient

    public class RPCAgentClient {
        private String remoteHost;
        private int remotePort;
        private int localPort;
        private Channel channel;
    
        public static RPCAgentClient getNewClient(String remoteHost, int remotePort) {
            RPCAgentClient rpcAgentClient = new RPCAgentClient();
            rpcAgentClient.remoteHost = remoteHost;
            rpcAgentClient.remotePort = remotePort;
            rpcAgentClient.localPort = new Random().nextInt(15000) + 10000;
    
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(new NioEventLoopGroup(1))
                        .channel(NioDatagramChannel.class)
                        .handler(new ChannelInboundHandlerAdapter());
                bootstrap.localAddress(rpcAgentClient.localPort);
                rpcAgentClient.channel = bootstrap.bind().channel();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return rpcAgentClient;
        }
    
        public void close() {
            if (channel != null) {
                channel.close();
            }
        }
    
        public void sendMsg(String msg) {
            try {
                channel.writeAndFlush(buildUdpMsg(remoteHost, remotePort, msg));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        private static DatagramPacket buildUdpMsg(String remoteHost, int remotePort, String msg) {
            return new DatagramPacket(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8),
                    new InetSocketAddress(remoteHost, remotePort));
        }
    }
    

    4、服务端测试类 NettyServerTest

    public class NettyServerTest {
    
        public static final int port = 8289;
    
        public static void main(String[] args) {
            try {
                new RPCAgentServer().listen(port);
                System.out.println(" service start finish ");
    
                System.out.println(new Scanner(System.in).next());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    5、 客户端测试类 NettyClientTest,启动 30 个客户端并发送消息 public class NettyClientTest {

        private static final String remoteHost = "127.0.0.1";
    
        public static void main(String[] args) {
            System.out.println("start start devices");
    
            ExecutorService executorService = Executors.newFixedThreadPool(30);
            for (int i = 0; i < 30; i++) {
                executorService.execute(() -> {
                    RPCAgentClient rpcAgentClient = RPCAgentClient.getNewClient(remoteHost, NettyServerTest.port);
                    rpcAgentClient.sendMsg("1");
                    rpcAgentClient.close();
                });
            }
    
            try {
                executorService.shutdown();
                if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            System.out.println("start devices finish");
        }
    }
    

    启动顺序是先启动 NettyServerTest,再启动 NettyClientTest,观察 NettyServerTest 输出的计数。

    以现在的代码 NettyServerTest 输出的是正确的数量 30

    但是如果我在 RPCAgentClient 的 getNewClient 方法上加上 synchronized 关键字,不要问我为什么要加。。getNewClient 方法就变成

    public synchronized static RPCAgentClient getNewClient(String remoteHost, int remotePort)
    

    那么 NettyServerTest 输出的计数将小于 30,而且在我的电脑上的结果是小于 20。

    那么问题来了,这是为什么?

    5 条回复    2019-05-30 21:32:36 +08:00
    sagaxu
        1
    sagaxu  
       2019-05-30 00:22:21 +08:00 via Android
    跟 synchronized 无关,netty 用的不对
    imzhoukunqiang
        2
    imzhoukunqiang  
       2019-05-30 09:10:27 +08:00   ❤️ 1
    一楼说的对,跟 synchronized 无关,netty 建立连接是一个异步操作,你没有等待就直接把 channel 发布出去了。
    解决方法是把 getNewClient 方法的 rpcAgentClient.channel = bootstrap.bind().channel(); 改成 rpcAgentClient.channel = bootstrap.bind().sync().channel();
    ColoThor
        3
    ColoThor  
    OP
       2019-05-30 10:41:00 +08:00
    @imzhoukunqiang 谢谢,的确是这个问题。我看了下源码,channel 是 new 出来的,不为 null 不代表建立成功。但我还是不明白为什么加了 synchronized 更容易暴露问题
    imzhoukunqiang
        4
    imzhoukunqiang  
       2019-05-30 14:39:17 +08:00   ❤️ 1
    @ColoThor 我猜测是 synchronized 导致线程切换。这种不安全的操作,讨论 synchronized 其实意义不大,不用过多纠结。
    ColoThor
        5
    ColoThor  
    OP
       2019-05-30 21:32:36 +08:00
    @imzhoukunqiang 好的,谢谢回答
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1227 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 23:22 · PVG 07:22 · LAX 16:22 · JFK 19:22
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.