美文网首页
网络、io和多线程

网络、io和多线程

作者: 安然在路上 | 来源:发表于2020-02-01 20:52 被阅读0次

    说明:io部分来自李刚的《疯狂Java讲义》

    1、网络

    OSI参考模型:
    应用层:所有能够产生网络流量的程序
    表示层:在传输之前是否进行加密 或 压缩处理
    会话层:可以查木马 netstat -n
    传输层:可靠传输 流量控制 不可靠传输
    网络层:负责选择最佳路径 规划IP地址
    数据链路层:帧的开始和结束 透明传输(中间加入转义符) 差错检验
    物理层:接口标准 电器标准 如何在物理链路上传输更快的速度

    1580702783(1).png 1580904402(1).png

    TCP:需要将要传输的文件分段 传输 建立会话 可靠传输 流量控制 比如qq传文件
    UDP:一个数据包就能够完成数据通信 不分段 不需要建立会话 不需要流量控制 不可靠传输 比如 qq聊天


    1580905970(1).png
    1580906108(1).png

    如何查看服务侦听的端口:
    netstat -an
    netstat -n 查看建立的会话
    netstat -nb 查看建立会话的进程
    telnet 192.168.80.100 3389 测试远程计算机某个端口是否打开

    传输层(TCP/UDP协议):提供应用进程间的逻辑通信
    网络层(IP协议):提供主机之间的逻辑通信

    TCP:
    面向连接的传输层协议
    每一条TCP连接只能有两个端点(endpoint),每一条TCP连接只能是点对点的(一对一)
    TCP提供可靠交付的服务
    TCP提供全双工通信
    面向字节流
    TCP把连接作为最基本的抽象
    TCP连接的端点不是主机,不是主机的IP地址,不是应用程序,也不是传输层协议端口,TCP连接的端点叫做套接字(socket).
    端口号拼接到IP地址即构成了套接字。


    1581043964(1).png

    服务使用 TCP或UDP的端口侦听客户端请求
    客服端使用IP地址定位服务器 使用目标端口定位服务

    DNS服务作用:
    负责解析域名 将域名解析成IP
    域名解析测试:
    ping www.baidu.com
    nslookup www.baidu.com

    使用web代理服务器访问网站:
    1、节省内网访问Internet的带宽
    2、通过Web代理绕过防火墙

    恶意程序:
    1、计算机病毒——会“传染”其他程序的程序
    2、计算机蠕虫——通过网络的通信功能将自身从一个结点发送到另一个结点并启动运行的程序。(吃CPU和内存)
    3、特洛伊木马——它执行的功能超出所声称的功能
    4、逻辑炸弹——当运行环境满足某种特定条件时执行其他特殊功能的程序

    ARP协议:将IP地址通过广播(目标MAC地址是FF-FF-FF-FF-FF-FF)解析目标IP地址的MAC地址,扫描本网段MAC地址。

    tracert baidu.com.cn 可以跟踪路由器
    网络通的关键,数据有去有回
    静态路由:需要管理员告诉路由器所有没有直连的网络下一跳给谁。
    动态路由:RIP协议 周期性广播路由表 跳数 30秒更新一次路由信息 最大跳数15跳

    FTP(文件传输)协议:
    连接方式:
    控制连接:标准端口为21,用于发送FTP命令信息
    数据连接:标准端口为20,用于上传、下载数据
    数据连接的建立类型:
    主动模式 服务端从20端口主动向客户端发起连接
    被动模式 服务端在指定范围内的某个端口被动等待客户端发起连接
    FTP服务器端如果有防火墙 需要在防火墙开20和21端口 使用主动模式进行数据连接

    因特网提供的音频/视频服务大体上可分为三种类型:
    1、流式存储音频/视频 ——边加载边播放
    节省客户端硬盘空间 不用下载 保护视频版权
    2、流式 实况音频/视频 ——边录制边发送
    通过网络现场直播
    3、交互式音频/视频 ——实时交互式通信

    在Internet上传输音频视频面临哪些问题:
    1、延迟 对于非交互式的音频视频影响不大
    2、带宽不稳定

    2、NIO(New IO)

    将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(模拟了操作系统上的虚拟内存的概念)。
    

    Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象。
    Channel是对传统的输入/输出系统的模拟,在新IO中所有的数据都需要通过通道传输。
    Buffer可以理解为一个容器,本质是一个数组,发送到Channel中的所有对象都必须先放到Buffer中,而从Channel中读取的数据也必须先放到Buffer中。
    实际使用较多的是ByteBuffer和CharBuffer。其中ByteBuffer类还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回。

    在Buffer中有3个重要的概念:容量(capacity)、界限(limit)和位置(position)。

    • 容量(capacity):缓冲区的容量(capacity)表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,创建后不能改变。
    • 界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写。
    • 位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中第3个(第1个位置的索引为0)位置。

    当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将limit设置为position所在位置,并将position设为0,这就使得Buffer的读写指针又移到了开始位置。也就是说,Buffer调用flip()方法之后,Buffer为输出数据做好准备;
    当Buffer输出数据结束后,Buffer调用clear()方法,clear()方法不是清空Buffer的数据,它仅仅将position置为0,将limit置为capacity,这样为再次向Buffer中装入数据做好准备。

    所有的Channel都不应该通过构造器来直接创建,而是通过传统的节点InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获得的Channel不一样。

    Channel中最常用的3类方法是map()read()write(),其中map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer;而read()或write()方法都有一系列重载形式,这些方法用于从Buffer中读取数据或向Buffer中写入数据。

    下面程序示范了直接将FileChannel的全部数据映射成ByteBuffer的效果。

    public class FileChannelTest
            {
                public static void main(String[] args)
                {
                      File f=new File("FileChannelTest.java");
                      try(
                            // 创建FileInputStream,以该文件输入流创建FileChannel
                            FileChannel inChannel=new FileInputStream(f).getChannel();
                            // 以文件输出流创建FileChannel,用以控制输出
                            FileChannel outChannel=new FileOutputStream("a.txt")
                                .getChannel())
                      {
                            // 将FileChannel里的全部数据映射成ByteBuffer
                            MappedByteBuffer buffer=inChannel.map(FileChannel
                                .MapMode.READ_ONLY , 0 , f.length());   // ①
                            // 使用GBK的字符集来创建解码器
                            Charset charset=Charset.forName("GBK");
                            // 直接将buffer里的数据全部输出
                            outChannel.write(buffer);    // ②
                            // 再次调用buffer的clear()方法,复原limit、position的位置
                            buffer.clear();
                            // 创建解码器(CharsetDecoder)对象
                            CharsetDecoder decoder=charset.newDecoder();
                            // 使用解码器将ByteBuffer转换成CharBuffer
                            CharBuffer charBuffer=decoder.decode(buffer);
                            // CharBuffer的toString方法可以获取对应的字符串
                            System.out.println(charBuffer);
                      }
                      catch (IOException ex)
                      {
                            ex.printStackTrace();
                      }
                }
            }
    

    虽然FileChannel既可以读取也可以写入,但FileInputStream获取的FileChannel只能读,而FileOutputStream获取的FileChannel只能写。

    3、多线程(并发编程)

    互斥同步(Mutual Exclusion & Synchronization)是一种最常见也是最主要的并发正确性保障手段。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条(或者是一些,当使用信号量的时候)线程使用。而互斥是实现同步的一种手段,临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是常见的互斥实现方式。因此在“互斥同步”这四个字里面,互斥是因,同步是果;互斥是方法,同步是目的。

    3.1 Semaphore和Exchanger

    类Semaphore的主要作用是限制并发执行的线程个数。
    

    在类A中使用Semaphore作为全局变量,类A是单例模式的,就可以实现控制线程并发数的功能。semaphore.acquire()和semaphore.release()之间的代码只有有限线程可以访问,其他处于阻塞状态。

    import java.util.concurrent.Semaphore;
            public class Service {
                private Semaphore semaphore = new Semaphore(1);
                public void testMethod() {
                    try {
                        semaphore.acquire();
                        System.out.println(Thread.currentThread().getName()
                                      + " begin timer=" + System.currentTimeMillis());
                              Thread.sleep(5000);
                              System.out.println(Thread.currentThread().getName()
                                      + "    end timer=" + System.currentTimeMillis());
                              semaphore.release();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                }
    
    类Exchanger可以使2个线程之间传输数据,比生产者/消费者模式使用的wait/notify更加方便。
    

    创建ThreadB.java类代码如下:

    package extthread;
         import java.util.concurrent.Exchanger;
         public class ThreadA extends Thread {
            private Exchanger<String> exchanger;
            public ThreadA(Exchanger<String> exchanger) {
                super();
                this.exchanger = exchanger;
            }
            @Override
            public void run() {
                try {
                    System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("中国人A"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    

    创建ThreadB.java类代码如下:

        package extthread;
        import java.util.concurrent.Exchanger;
        public class ThreadB extends Thread {
            private Exchanger<String> exchanger;
            public ThreadB(Exchanger<String> exchanger) {
                super();
                this.exchanger = exchanger;
            }
            @Override
            public void run() {
                try {
                    System.out.println("在线程B中得到线程A的值=" + exchanger.exchange("中国人B"));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    

    运行类Run.java代码如下:

        package test.run;
        import java.util.concurrent.Exchanger;
        import extthread.ThreadA;
        import extthread.ThreadB;
        public class Run {
            public static void main(String[] args) {
                      Exchanger<String> exchanger = new Exchanger<String>();
                      ThreadA a = new ThreadA(exchanger);
                      ThreadB b = new ThreadB(exchanger);
                      a.start();
                      b.start();
                  }
              }
    

    程序运行后输出:
    在线程B中得到线程A的值=中国人A
    在线程A中得到线程B的值=中国人B

    3.2 CountDownLatch的使用

    类CountDownLatch所提供的功能是判断count计数不为0时则当前线程呈wait状态,也就是在屏障处等待。
    

    实现等待与继续运行的效果分别需要使用await()和countDown()方法来进行。调用await()方法时判断计数是否为0,如果不为0则呈等待状态。其他线程可以调用count-Down()方法将计数减1,当计数减到为0时,呈等待的线程继续运行。

    3.3 Excutor

    没有灵魂的executor类图

    接口Executor仅仅是一种规范,并没有实现任何的功能,所以大多数的情况下需要使用接口的实现类来完成指定的功能,比如ThreadPoolExecutor类就是Executor的实现类,但ThreadPoolExecutor在使用上并不是那么方便,在实例化时需要传入很多个参数,还要考虑线程的并发数等与线程池运行效率有关的参数,所以官方建议使用Executors工厂类来创建线程池对象
    使用线程池实现线程的复用示例:
    类MyRunnable.java代码如下:

            package myrunnable;
            class MyRunnable implements Runnable {
                private String username;
                public MyRunnable(String username) {
                    super();
                    this.username = username;
                }
                @Override
                void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + " username="
                                  + username + " begin " + System.currentTimeMillis());
                        Thread.sleep(2000);
                        System.out.println(Thread.currentThread().getName() + " username="
                                  + username + "    end " + System.currentTimeMillis());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    

    运行类Run.java代码如下:

            package test.run;
            import java.util.concurrent.ExecutorService;
            import java.util.concurrent.Executors;
            import myrunnable.MyRunnable;
            Public class Run {
                Public static void main(String[] args) {
                    ExecutorService executorService = Executors.newCachedThreadPool();
                    for (int i = 0; i < 10; i++) {
                        executorService.execute(new MyRunnable(("" + (i + 1))));
                    }
                }
            }
    

    程序运行结果如下图所示。


    线程池复用线程

    3.4 Callable和Future

    接口Callable与线程功能密不可分,但和Runnable的主要区别为:
    1)Callable接口的call()方法可以有返回值,而Runnable接口的run()方法没有返回值。
    2)Callable接口的call()方法可以声明抛出异常,而Runnable接口的run()方法不可以声明抛出异常。
    执行完Callable接口中的任务后,返回值是通过Future接口进行获得的。但是有阻塞性。
    future使用示例:

    import java.util.concurrent.Callable;
        import java.util.concurrent.ExecutionException;
        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Future;
        import java.util.concurrent.LinkedBlockingDeque;
        import java.util.concurrent.ThreadPoolExecutor;
        import java.util.concurrent.TimeUnit;
        public class Test3 {
            public static void main(String[] args) {
                try {
                    ExecutorService executor = new ThreadPoolExecutor(50,
                              Integer.MAX_VALUE, 5, TimeUnit.SECONDS,
                              new LinkedBlockingDeque<Runnable>());
                    Future future = executor.submit(new Callable<String>() {
                        @Override
                        public String call() throws Exception {
                              Integer.parseInt("a");
                              return "我是返回值";
                        }
                    });
                    System.out.println(future.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                    System.out.println("能捕获异常");
                }
            }
        }
    

    3.5 ScheduledExecutorService

    类ScheduledExecutorService的主要作用就是可以将定时任务与线程池功能结合使用。
    

    3.6 集合

    3.6.1 List

    接口List最常用的非并发实现类就是ArrayList,它是非线程安全的,是有序的。
    类ArrayList并不是线程安全的,如果想使用线程安全的链表则可以使用Vector类

    3.6.2 Set

    接口Set也是对Collection接口进行了扩展,它具有的默认特点是内容不允许重复,排序方式为自然排序,防止元素重复的原理是元素需要重写hashCode()和equals()方法。
    接口Set最常用的不支持并发的实现类就是HashSet。HashSet默认以无序的方式组织元素,而LinkedHashSet类可以有序的组织元素。另一个实现类TreeSet,不仅实现了Set接口,还实现了SortedSet和NavigableSet接口,所以有序。

    3.6.3 非阻塞队列
    非阻塞队列的特色就是队列里面没有数据时,操作队列出现异常或返回null,不具有等待/阻塞的特色。和普通的容器使用方式相同。
    

    常见的非阻塞队列有:
    1)ConcurrentHashMap;
    2)ConcurrentSkipListMap;
    3)ConcurrentSkipListSet;(有序)
    4)ConcurrentLinkedQueue;
    5)ConcurrentLinkedDeque;
    6)CopyOnWriteArrayList;(ArrayList升级版)
    7)CopyOnWriteArraySet。(解决多线程环境下的HashSet不安全的问题)

    ConcurrentLinkedQueue
    poll():如果有数据时则移除表头,并将表头进行返回。
    peek():如果有数据时则不移除表头,并将表头进行返回。

    3.6.4 阻塞队列

    如果BlockQueue空,从BlockingQueue取东西的操作将会被阻塞进入等待状态,直到BlockingQueue添加进了元素才会被唤醒。同样,如果BlockingQueue是满的,试图往队列中存放元素的操作也会被阻塞进入等待状态,直到BlockingQueue里有剩余空间才会被唤醒继续操作。

    相关文章

      网友评论

          本文标题:网络、io和多线程

          本文链接:https://www.haomeiwen.com/subject/jaixxhtx.html