synchronized 引发的问题,进来解答一下 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ColoThor
V2EX    Java

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

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

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

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

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

    public class GetDataHandlerAdapter extends ChannelInboundHandlerAdapter { private static Logger lgger = 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 用的不对
    cookii
        2
    cookii  
       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 更容易暴露问题
    cookii
        4
    cookii  
       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     Solana     1038 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 19ms UTC 23:36 PVG 07:36 LAX 15:36 JFK 18:36
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86