Netty应用(六) 之 异步 Channel

news2024/11/24 18:42:58

目录

12.Netty异步的相关概念

12.1 异步编程的概念

12.2 方式1:主线程阻塞,等待异步线程完成调用,然后主线程发起请求IO

12.3 方式2:主线程注册异步线程,异步线程去回调发起请求IO

12.4 细节注释

12.5 异步的好处

13.Netty中异步设计(内部原理)

13.1 关于Future

13.1.1 JDK中的future

13.1.2 netty中的future

13.2 关于promise

13.3 本章问题

14.Channel

14.1 netty封装之后的常见API

14.1.1 writeAndFlush(Object msg)

14.1.2 write(Object msg)

14.1.3 close

14.1.4 优雅的关闭 shutdownGracefully()


12.Netty异步的相关概念

12.1 异步编程的概念

异步编程和多线程编程

多线程编程:

多线程编程中每一个线程都是公平,平等的关系,举例子:多个客户端client请求与服务端交互,假设说服务端给每一个客户端都开启一个线程去处理,那么这多个线程是公平,平等的

异步编程:

通常是在一个主要的线程(main线程)中,异步开启一个其他线程去处理一些阻塞耗时的操作,但是当该异步开启的其他线程处理完耗时阻塞的操作后,要把最终得出的结果返回给主要的线程(main线程)。异步编程是分主次的,而不是平等的关系。

不过二者都是多线程!

异步编程有两种方式:

1、主线程阻塞,等待连接线程完成调用,然后主线程发起请求IO。

这个方式不好,因为执行下面发送IO的操作是在主线程,他阻塞等待了连接的异步线程。实际上还是在等待。

2、主线程注册异步线程,异步线程去回调发起请求IO。

这种方式是更高效的,因为主线程没有阻塞,他等着回调就完了,主线程继续往下做他的事。

在Netty中有一个原则:

如果是关于网络IO的地方,都设计为异步处理。比如上面说的connect(),网络连接方法自然是网络IO,是异步的。

还有就是我们代码中的channel.writeAndFlush("hello netty");这里再写数据就是IO,也是异步线程执行的。

所以如果你的channel.writeAndFlush("hello netty");这一行后面需要他的返回结果,也需要阻塞等待。类似这样。

Channel channel = future.channel();

ChannelFuture writeAndFlushFuture = channel.writeAndFlush("hello netty");

writeAndFlushFuture.sync();

还有就是.close()关闭socket也是异步的。关闭socket自然要做网络IO。

所以就是一旦是异步,如果你下面要处理就需要考虑阻塞等待,或者监听回调。

12.2 方式1:主线程阻塞,等待异步线程完成调用,然后主线程发起请求IO

package com.messi.netty_core_02.netty03;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyNettyServer {

    private static final Logger log = LoggerFactory.getLogger(MyNettyServer.class);

    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));

        DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();

        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(defaultEventLoopGroup,new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        log.info("服务端读取到的数据为:{}",msg);
                    }
                });
            }
        });

        serverBootstrap.bind(8000);

    }

}
package com.messi.netty_core_02.netty03;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient {

    private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);

    public static void main(String[] args) throws Exception{
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);

        bootstrap.group(new NioEventLoopGroup());
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));
        future.sync();

        Channel channel = future.channel();
        channel.writeAndFlush("leomessi");

        log.info("--------netty-sync--------");
        System.out.println("处理业务操作......");
        log.info("--------netty-sync--------");

    }

}
  • 测试与分析

存在性能瓶颈,影响后续业务操作,所以引出方式2

12.3 方式2:主线程注册异步线程,异步线程去回调发起请求IO

  • 代码
package com.messi.netty_core_02.netty03;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyNettyServer {

    private static final Logger log = LoggerFactory.getLogger(MyNettyServer.class);

    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));

        DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();

        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(defaultEventLoopGroup,new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        log.info("服务端读取到的数据为:{}",msg);
                    }
                });
            }
        });

        serverBootstrap.bind(8000);

    }

}
package com.messi.netty_core_02.netty03;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient {

    private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);

    public static void main(String[] args) throws Exception{
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);

        bootstrap.group(new NioEventLoopGroup());
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Channel channel = future.channel();
                channel.writeAndFlush("leomessi");
            }
        });

        log.info("--------netty-sync--------");
        System.out.println("处理业务操作......");
        log.info("--------netty-sync--------");

    }

}
  • 测试与分析

测试结果:

异步新线程回调注册监听的匿名内部类重写的方法:

12.4 细节注释

  • 细节1

为什么需要future.sync()使得main线程阻塞等待到异步线程处理完网络连接操作后才能让main线程继续执行?

因为网络连接操作是后续一切网络IO的基础,如果你网络连接这一io都没做好,那么你后续read,write这些网络io你怎么做?你没法保证,所以就像方式1那种main线程阻塞等待的方式,其实就是为了保证网络连接正确建立,避免后续其他网络io出问题。

但是由于main线程同步阻塞的方式太低效,所以才引出了后续注册监听,异步回调的方式来优化性能!也就是方式2。但是有一点仍然需要注意,我们要把网络io操作(read或write)放在异步线程处理完连接后所回调的回调方法中!为什么?还问为什么吗,见上即可,就是为了保证read,write这些网络io操作的执行是建立在正确无误完成网络连接之后的呀。对于一些无关网络io的业务操作,main线程由于不阻塞了,所以也可以执行。这样是不是把这个同步阻塞的范围从阻塞"整个main线程"缩小到只阻塞"必须网络连接完成后才能执行read,write等网络io"了。【代码详细见方式1和方式2】

12.5 异步的好处

1.提高系统的吞吐量

分析:这个肯定,可以在相同时间内处理更多的客户端操作或业务操作

2.效率上的提高

分析:原来需要t ms处理完的操作,异步后可能只需要t/2 ms。但绝对不是1+1=2,1+1<2,即两个线程达到的性能高度绝对不是理论上两个线程的性能水平,因为线程间通信,切换都需要耗费性能。

13.Netty中异步设计(内部原理)

先给一个结论:

jdk的future只支持阻塞同步式的future

netty的future是在jdk的future的基础上做的扩展,支持阻塞同步式的future,也支持异步监听处理式的future

netty的Promise是在netty的future的基础上做的扩展,支持阻塞同步式的future,也支持异步监听处理式的future,同样支持得到异步监听的结果是正确还是失败的(这一点前面二者都是不支持的)

我们之前用的bootstrap.connect(new InetSocketAddress(8000));返回的就是ChannelFuture这个异步化的支持方式。

而在netty中还实现了Promise这个异步支持,他的功能是最全的,所以他用的比较多。而且实际上我们上面的代码中,ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));

这个ChannelFuture 其实也是promise的类型,可以断点验证一下:可以看到Future实际上是一个promise类型的内部类。Promise是netty中最底层的继承类,所以他最强大,所以自然也就用的多一些。

13.1 关于Future

我们上面看来netty中的future就是继承自JDK中的future,并且对他做了扩展。下面我们分别来看看他们的区别和差异

13.1.1 JDK中的future

测试:

13.1.2 netty中的future
  • 等待阻塞方式

测试:

  • 监听的方式

13.2 关于promise

后续编码Promise用的不多,Handler用的多,但是Netty底层Promise用的多

上面我们看到了netty中的future提供了异步监听的方式来实现异步处理,我们在future中异步执行了callable中的call方法,最后返回结果的时候回调了listener中的回调方法,把future当成参数传给了回调方法,我们在回调方法中就能通过future.get()获取到call中的执行结果了。

于是问题来了:

1.但是如果我们编程的时候用的不是callable,而是使用的Runnable,runnable是没有返回值的,你这时候这个操作就无法得到返回值了。于是这个当前的架构就不行了。

而我们说promise是继承自future(netty)的,它的功能更加强大了,它实际上就能解决这个Runnable没有返回值的问题,因为promise可以进行设置结果是成功还是失败。

2.有些人会说既然你Runnable不可以有返回值,那么使用Callable问题不就解决了吗?但是你应该清楚,即便我们使用了Callable接口,我们在call中返回的值在实际开发中可能是一个code码值,或者是一个调用结果。你如何知道这个返回结果是正常成功的还是失败的呢?你总的要告诉承接你这个结果的地方是正常的还是失败的吧。也就是你要正常和异常都要给出一个结果返回,你的异步到底执行的如何了。这个需要返回让外部其他线程(main线程)知道。

而我的主线程和你异步线程交互的东西就是异步的返回值,那么这时候就得知道这个返回。所以你不仅仅要把处理的数据作为返回,还要把处理的结果也返回。你也可以包一层比如一个类Result里面有data还有code码,你封装起来返回。但是这就引入新的类了,我们希望返回的数据就是数据,结果就是结果。于是netty为我们封装了一个新的类型就是Promise。

promise不仅仅告诉你结果返回的是什么,还告诉你结果是成功的还是失败的

  • promise的异步阻塞操作

测试:

  • promise的异步监听操作

测试:

  • 总结

Promise让无返回值的Runnable可以设置返回值并且设置返回值是正确还是失败的

13.3 本章问题

关于监听器的执行顺序问题

DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup(2);
EventLoop eventLoop = defaultEventLoopGroup.next();
// 1
Future<String> future = eventLoop.submit(new Callable<String>() {
	@Override
	public String call() throws Exception {
		logger.debug("call begin ***************");
		TimeUnit.SECONDS.sleep(2);
		logger.debug("thread is {}", Thread.currentThread().getName());
		return "hello netty";
	}
});
// 2
future.addListener(new GenericFutureListener<Future<? super String>>() {
		@Override
		public void operationComplete(Future<? super String> future) throws Exception
		{
			logger.debug("get call result is {}", future.get());
		}
})

之前我们有这么一段代码,我们说eventLoop提交了异步任务,异步线程去执行,然后下面的future添加监听器来监听,等到异步任务执行结束了,就回调这个监听器里面的operationComplete方法。既然是异步的多线程必然就有CPU调度问题,假如在1处的异步代码执行完了,2处的代码还没被调度,也就是异步执行完了,监听器还没注册进去,那你的监听器是不是就无法监听到异步结果了呢????意思就是你异步执行完了的回调是要回调谁呢?源码中利用兜底操作解决这一CPU调度问题。

来看源码:(future的实际执行类型是promise,也就是我们直接去DefaultPromise去查看这个addListener的方法)

// io.netty.util.concurrent.DefaultPromise#addListener
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>>
listener) {
		checkNotNull(listener, "listener");
		// 这里是添加监听器的地方
		synchronized (this) {
			addListener0(listener);
		}

    
// 这里是这个问题的重点,当异步线程执行完成后 isDone()返回true。否则返回false。
//如果返回true且你已经完成监听,那么回调operationComplete方法
//如果返回true但是你没监听,也就是你没有来得及监听,异步结果就执行完了,这里也会再次执行一遍监听操作
    ,添加进去然后进行回调
    //所以这其实是个兜底操作。
		if (isDone()) {
			notifyListeners();
		}
		return this;
}
  • WebFlux

WebFlux Spring响应式框架。

WebFlux百分之80都是Netty的API,其余扩展都是在Handler上做的扩展。

14.Channel

1.首先netty中的channel是对原来jdk中NIO下面的ServerSocketChannel和SocketChannel的封装

2.这个封装最终体现出来的有两个好处:

(1)统一了channel的编程模型,客户无需再区分ServerSocketChannel还是SocketChannel,都是bootstrap,channel

(2)提供了更加强大和丰富的功能,各种编解码处理器,TCP Socket缓冲区大小的设计。滑动窗口大小的设计。你像你之前使用NIO编程,你怎么设置这些操作系统级别的参数?设置不了的。但是netty这里可以,后续都会一一讲到这些API

这些增强的功能也就是我们需要关注的那些API实现

14.1 netty封装之后的常见API

14.1.1 writeAndFlush(Object msg)

以前我们在NIO的时候,写出数据用的是NIO的write(ByteBuffer byteBuffer),你要写一个字符串出去,还得把字符串转为ByteBuffer作为参数传递进去,才能写出去。现在Netty里面的writeAndFlush(Object msg)里面接收的是一个Object对象。你不用区分任何的类型,直接写就行了。

而且这个方法你一看名字就知道,写入并且刷新,意思就是写入到操作系统内核的Socket缓冲区后立马刷新通过网络发出。所以当你使用这个方法发数据时,服务端立马能接收到的。

但是注意:netty中网络IO都会异步开启一个新线程(NioEventLoop)来执行

14.1.2 write(Object msg)

这个方法也是接收的Object,不用转为ByteBuffer类型就能发送,不用问你在网络传输不能直接传字符串,肯定是要转的,netty其实也就是封装了NIO,所以他底层肯定给你转成了ByteBuffer,只是不用你做这个操作了,参数接收的更统一了。

但是这个方法就只是写出了,写到了操作系统socket缓冲区里面,除非缓冲区写满了,不然他不会写完就发出数据,你需要接一步,channel.flush()才能把数据及时刷出去。

14.1.3 close

以前我们刚开始写netty代码的时候是如下这样:

// 客户端连接端口到服务端,连接到了返回的ChannelFuture是个异步操作。,这里是新的线程处理的连接

ChannelFuture connect = bootstrap.connect(new InetSocketAddress(8000));

// 在这里阻塞,等到连接上了,成功了在返回,然后往下走

connect.sync();

// 连接上了,在这个连接获取通道,也就是当初NIO的SC。此时这里是main线程

Channel channel = connect.channel();

// 往服务端写数据,往出写

channel.writeAndFlush("hello netty");

channel发送完了数据之后就不管了,实际上这里之后程序还没有结束,因为启动了一些socket程序,是后台线程,导致这个程序不会结束。

但是实际上我们开发可能会要结束这个程序,就需要channel.close();加一句。

这句代码会帮你关闭一些后台资源。

但是这个时候有个需求,我们在执行完close之后要执行一些业务逻辑,这时候我们第一反应可能就是这样,有两种方案:1.同步阻塞等待close关闭完成 2.监听

  • 方案1
public class MyNettyClient {

    private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);

    public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap() ;
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group);
        bootstrap.channel(NioSocketChannel.class);

        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));
        future.sync();

        Channel channel = future.channel();
        channel.writeAndFlush("leomessi");

        System.out.println("MyNettyClient.main");

        ChannelFuture close = channel.close();

        close.sync();
        
        log.info("当channel关闭后,执行一些业务逻辑.....");
    }
}
  • 方案2:

  • 引入LoggingHandler

  • 测试

其实同步阻塞和监听都无所谓,测试一个即可,这里以监听方案进行测试:

  • 测试存在的问题和细节

你debug测试的时候,把断点处改为Thread,这样断点调试阻塞的是当前main线程,而不是所有线程。因为netty针对所有网络IO都是异步开启新线程的方式进行处理,你要是设置为ALL阻塞所有线程,你异步开启的新线程是不是也阻塞住了,那么你怎么write写出,怎么flush刷新给出去呢????都阻塞了,啥都别想了。。

  • 引入LoggingHandler打印输出日志,使用Grep这一功能

选中然后右键

看到如下日志:

14.1.4 优雅的关闭 shutdownGracefully()

我们可以看到一个现象:

我们看到日志显示事件和通道实际上被关闭了。但是左边的红点显示程序还没被关闭。这个不难理解, 我们上面用的是bootstrap.group(new NioEventLoopGroup());,这个NioEventLoopGroup被创建出来,其实是个线程池,这个线程池还活着呢,所以还有后台线程在,你这个程序结束不掉。netty认为直接结束不安全,万一除了通道事件还有别的线程在做任务,这样直接全结束不安全。

  • 补充

因为我们new NioEventLoopGroup(),按照netty以及本计算机核数来计算的话,创建出32个线程。虽然说NioEventLoopGroup的线程是被用作网络IO做异步线程开启。但是假设说如果使用得当,当我们处理普通任务时,也可以使用NioEventLoopGroup中的线程去做异步(只需要把任务提交给NioEventLoop即可)。

当channel.close()调用后,close方法只占用一个NioEventLoop线程,其他31个线程就算不都运行着,其中可能还有很多线程是有未执行完的任务的,所以如果channel.close()后就关闭整个client的所有线程,那么太不地道,太绝户了。所以使用的是eventLoopGroup.shutdownGracefully(),优雅的关闭,当所有的线程(32个线程)都结束处理之后,才会真正关闭client

  • 所以引入优雅的关闭:eventLoopGroup.shutdownGracefully()

测试:

  • 那么问题来了,如果我们不关闭通道,直接优雅关闭会咋样。试一下:
package com.messi.netty_core_02.netty04;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient {

    private static final Logger log = LoggerFactory.getLogger(MyNettyClient.class);

    public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap() ;
        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group);
        bootstrap.channel(NioSocketChannel.class);

        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));
        future.sync();

        Channel channel = future.channel();
        channel.writeAndFlush("leomessi");

        System.out.println("MyNettyClient.main");

//        ChannelFuture close = channel.close();

//        close.sync();
//
//        log.info("当channel关闭后,执行一些业务逻辑.....");
//        close.addListener(new GenericFutureListener<Future<? super Void>>() {
//            @Override
//            public void operationComplete(Future<? super Void> future) throws Exception {
//                log.info("当channel关闭后,执行一些业务逻辑.....");
//            }
//        });
//        //优雅的关闭
        group.shutdownGracefully();


    }

}

测试结果:

这个优雅的关闭同样会close关闭channel,但是不会打印输出,可能底层源码走的逻辑不一样,但是肯定是把channel关闭了,因为状态已经显示为INACTIVE。为什么会这样?因为对于channel的操作,比如说连接或关闭channel通道,其实都是NioEventLoopGroup其中一个线程去做的,那么优雅的关闭是关闭该线程组中的所有,只不过是等待他们都做完自己的线程任务再优雅的关闭,最终都会关闭。

实际就是结束的客户端,既然是优雅关闭,他关闭的实际就是等待EventLoopGroup里面所有线程的任 务都结束才完成关闭,实际上这就是优雅关闭的定义,后面好好研究一下优雅关闭(kill -15和kill -9的故事,以及jdk中对于Linux关闭事件的捕获实现)。

注释:kiil -9 +进程号A :强制关闭进程号A所对应的进程,即使你进程A中还有很多线程没有执行完毕,也得强制关闭掉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1444968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Android】使用Android Studio运行Hello World项目

文章目录 1. JDK的安装与配置2. Android Studio的安装3. 运行Hello World项目3.1 新建项目3.2 修改项目配置3.2.1 修改UI界面3.2.2 配置 Android SDK 3.3 添加并运行虚拟设备3.4 运行项目 1. JDK的安装与配置 想要使用Android Studio&#xff0c;必须先配置Java环境&#xff0…

机器学习之监督学习和非监督学习

监督学习(Supervised Learning)监督学习是一种学习方式&#xff0c;其中模型从标记的训练数据中学习。这意味着每个训练样本都是由输入向量和相应的目标输出(也称为标签)组成的。模型的任务是学习输入到输出的映射函数&#xff0c;以便当提供新的、未见过的数据时&#xff0c;模…

MyBatis中#和$符的区别,sql注入问题,动态sql语句

MyBatis中#{}和${}的区别 #{}和${}都是MyBatis提供的sql参数替换。区别是&#xff1a;#{}是预编译处理&#xff0c;${}是字符串直接替换。#{}可以防止SQL注入&#xff0c;${}存在SQL注入的风险&#xff0c;例如 “ or 11”虽然存在SQL注入风险&#xff0c;但也有自己的适用场…

CSS Selector—选择方法,和html自动——异步社区的爬取(动态网页)——爬虫(get和post的区别)

这里先说一下GET请求和POST请求&#xff1a; post我们平时是要加data的也就是信息&#xff0c;你会发现我们平时百度之类的 搜索都是post请求 get我们带的是params&#xff0c;是发送我们指定的内容。 要注意是get和post请求&#xff01;&#xff01;&#xff01; 先说一下异…

python基于flask的网上订餐系统769b9-django+vue

课题主要分为两大模块&#xff1a;即管理员模块和用户模块&#xff0c;主要功能包括个人中心、用户管理、菜品类型管理、菜品信息管理、留言反馈、在线交流、系统管理、订单管理等&#xff1b; 如果用户想要交换信息&#xff0c;他们需要满足双方交换信息的需要。由于时间有限…

2.10日学习打卡----初学RocketMQ(一)

2.10日学习打卡 对于MQ(Message queue)消息队列的一些解释可以看我原来写的文章 初学RabbitMQ 各大MQ产品比较 一.RocketMQ概述 发展历程 RocketMQ概念术语 生产者和消费者 生产者负责生产消息&#xff0c;一般由业务系统负责生产消息&#xff0c;消费者即后台系统&…

算法-3-基本的数据结构

单双链表 1.单链表双链表如何反转 import java.util.ArrayList; import java.util.List;public class Code01_ReverseList {public static class Node {public int value;public Node next;public Node(int data) {value data;}}public static class DoubleNode {public int…

C/C++模板初阶

目录 1. 泛型编程 2. 函数模板 2.1 函数模板概念 2.1 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 3. 类模板 3.1 类模板的定义格式 3.2 类模板的实例化 1. 泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int&…

微软和苏黎世联邦理工学院开源SliceGPT创新压缩技术节省大量部署资源;OpenAI成立儿童安全团队,防AI误用

&#x1f989; AI新闻 &#x1f680; 微软和苏黎世联邦理工学院开源SliceGPT创新压缩技术节省大量部署资源 摘要&#xff1a;微软和苏黎世联邦理工学院研究人员开源了SliceGPT&#xff0c;通过对大模型的权重矩阵进行压缩切片&#xff0c;实现了模型紧缩&#xff0c;节省了部…

CSS盒子的概念

盒子模型 盒子的概念 页面中的每一个标签都可以看做是一个“盒子”&#xff0c;通过盒子的视角更方便的进行布局 浏览器在渲染&#xff08;显示&#xff09;网页时&#xff0c;会将网页中的元素看做是一个个的矩形区域&#xff0c;称之为“盒子” 盒子模型 CSS中规定每个盒…

JAVA设计模式之迭代器模式详解

迭代器模式 1 迭代器模式介绍 迭代器模式是我们学习一个设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。在绝大多数编程语言中&#xff0c;迭代器已经成为一个基础的类库&#xff0c;直接用来遍历集合对象。在平时开发中&#xff0c;我们更多的是直接使用它&a…

联想thinkpad-E450双系统升级记

早期笔记本联想thinkpad-E450双系统 大约16年花4000多大洋&#xff0c;买了一台thinkpad-E450屏幕是16寸本&#xff0c;有AMD独立显卡&#xff0c;i5cpu&#xff0c;4G内存。 . 后来加了一个同型号4G内存组成双通道&#xff0c; . 加了一个三星固态500G&#xff0c; . 换了一个…

如何编写高效的可复用程序

子程序、FB 、FC等的相关内容还可以查看下面文章链接&#xff1a; https://rxxw-control.blog.csdn.net/article/details/124524693https://rxxw-control.blog.csdn.net/article/details/124524693 1、FB和FC编程的优点 待续.....

前置++与后置++

a相当于a的值在其中运算 a

02 动力云客之登陆界面

1. 前端登录界面 需求样式: 1. 自定义登录页面装配到main.js src下新建一个文件夹view, view下新建一个Vue Component , 名为LoginView.vue , 并选择options API Composition API用于复杂页面. 生成的LoginView.vue文件 <script> export default {//组件的名字nam…

【2024.02.11】定时执行专家 V6.9 龙年春节版 - 下载地址更新日志

目录 ◆ 最新版下载链接 ◆ 软件更新日志 – TimingExecutor Full Change Log ▼2024-02-11 V6.9 ▼2023-06-16 V6.8.2 ▼2023-02-27 V6.7 ▼ 2023-01-23 V6.6 ▼ 2023-01-20 V6.5 ▼ 2022-12-25 V6.4 ▼ 2022-11-15 V6.3 ▼ 2022-10-01 V6.2 ▼ 2022-07-…

苍穹外卖实操笔记六---缓存商品,购物车功能

苍穹外卖实操笔记六—缓存商品&#xff0c;购物车功能 一.缓存菜品 可以使用redis进行缓存&#xff1b;另外&#xff0c;在实现缓存套餐时可以使用spring cache提高开发效率&#xff1b;   通过缓存数据&#xff0c;降低访问数据库的次数&#xff1b; 使用的缓存逻辑&#…

uniapp微信小程序开发踩坑日记:Pinia持久化

如果你使用过Pinia&#xff0c;那你应该知道Pinia持久化插件&#xff1a;https://prazdevs.github.io/pinia-plugin-persistedstate/zh/ 但由于官方文档提供的说明并不是针对小程序开发&#xff0c;所以我们在使用这个插件实现uniapp小程序开发中Pinia持久化会出现问题 我在C…

ubuntu中尝试安装ros2

首先&#xff0c;ubuntu打开后有个机器人栏目&#xff0c;打开后&#xff0c;有好多可选的&#xff0c;看了半天 ,好像是博客&#xff0c;算了&#xff0c;没啥关系&#xff0c;再看看其他菜单 这些都不是下载链接。先不管&#xff0c;考虑了一下&#xff0c;问了ai&#xff…

Z-Stack一直卡在HAL_BOARD_INIT();

原因是Debugger没有配置好&#xff0c;因为默认是Simulator&#xff0c;不是TI的驱动&#xff0c;所以仿真出现一直卡在 HAL_BOARD_INIT(); 的情况&#xff0c;解决方法就是将Simulator改为Texas Instruments 改成下面的样子