首页 > 其他分享 >8 网络&多线程

8 网络&多线程

时间:2022-12-08 00:00:14浏览次数:33  
标签:Socket Thread Worker 网络 线程 new 多线程 public

Head First Java 和 AcWing Java课程做的总结8。

所有网络运作的低层细节都已经由java.net函数库处理掉了。

传送与接收网络上的数据只不过是链接上使用不同的链接串流的输入输出。如果有BufferedWriterBufferedReader就可以读取或写出。

socket来链接外面的世界,用多线程``multithread`来一边发消息,一边接收。

  • 客户端的socket
  • 服务器的socket

8.1 网络socket连接

客户端建立Socket连接:

  • Socket是个代表两台机器之间网络连接的对象。(java.net.Socket)

  • 网络设备:一种让运行在Java虚拟机上的程序能够找到方法去通过实际的硬件(比如网卡)在机器之间传送数据的机制。有人会负责这些低层的工作,比如操作系统的特定部分与Java的网络API。

  • //创建举例,参数是IP地址和端口号
    Socket chatSocket = new Socket("192.164.1.103", 5000);
    
    //连接的建立代表两台机器之间存有对方的信息
    
  • TCP端口

    • 一个16位宽,用于识别服务器上特定程序的数字;
    • 0—1023已留给已知的特定服务;1024—65535自用;
    • 逻辑上用来表示应用程序的数字;
    • 举例:HTTP:80;Telnet:23;POP3:110;SMTP:25
    • 不同程序不可以共享一个端口,绑定。

服务器端建立Socket连接:

  • 服务器端会有一对Socket

    • 一个等待用户请求的ServerSocket
    • 一个用户通信用的Socket
  • 工作方式

    • //服务器应用程序对特定端口创建出ServerSocket
      ServerSocket serverSocket = new ServerSocket(5000);
      /*服务器应用程序开始监听来自5000端口的客户端请求*/
      
      
      //客户端对服务器应用程序建立Socket连接
      Socket sock = new Socket("192.164.1.103", 5000);
      /*客户端得知道IP地址和端口号,在客户端代码中*/
      
      
      //服务器创建出与客户端通信的新Socket
      Socket sock = serverSocket.accept();
      
    • accept()方法会在等待用户Socket连接时闲置着。当用户连上来时,此方法会返回一个Socket(在不同的端口上)以便于客户端通信。SocketServerSocket的端口不相同,因此ServerSocket可以空出来等待其他的用户。

读取Socket:

  • 使用BufferedReadersocket上读取数据;

  • Java的好处在于,大部分输入、输出工作不在乎链接串流的上游实际是什么。

  • //建立对服务器的Socket连接
    Socket chatSocket = new Socket("127.0.0.1", 5000);
    
    //建立连接到Scocket上低层输入串流的InputStreamReader,将字节->字符
    InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream());
    
    //建立BufferedReader来读取
    BufferedReader reader = new BufferedReader(stream);
    //将BufferedReader链接到InputStreamReader
    
    String message = reader.readLine();
    
    //关闭所有串流
    reader.close();
    

写数据到Socket

  • 使用PrintWriter写数据到Socket上;

  • 每次都被写入一个String,所以PrintWriter是最标准的做法

  • //对服务器建立Socket连接
    Socket chatSocket = new Socket("127.0.0.1", 5000);
    
    //建立链接到Socket的PrintWriter
    PrintWriter writer = new PrintWriter(chatSocket.getOutputStream());
    //PrintWriter是字符数据和字节间的转换桥梁,可以衔接String和Socket两端
    
    
    //写入数据
    writer.println("message to send:");
    writer.print("another message");
    

8.2 客户端程序举例

//客户端代码
import java.io.*;
import java.net.*;

public class DailyAdviceClient{
    public void go(){
        try{
            Socket s = new Socket("127.0.0.1", 5000);

            InputSreamReader streamReader = new InputStreamReader(s.getInputStream());
            //链接数据串流
            BufferedReader reader = new BufferedReader(streamReader);

            String advice = reader.readLine();
            System.out.println("Today you should:" + advice);

            reader.close();
        }catch(IOException ex){
            ex.printStackTrace();
    	}
    }
    
    public static void main(String[] args){
        DailyAdviceClient client = new DailyAdviceClient();
        client.go();
    }
}

8.2 服务器程序举例

//服务器端代码

import java.io.*;
import java.net.*;

public class DailyAdviceServer{
    String[] adviceList = {/*一堆字符串*/};
    
    public void go(){
        try{
            ServerSocket serverSock = new ServerSocket(5000);
        
            //服务器进入无穷循环等待服务客户端的请求
            while(true){

                Socket sock = serverSock.accept();
                //卡在这知道请求到达

                PrintWriter writer = new PrintWriter(sock.getOutputStream());

                String advice = getAdvice();
                writer.println(advice);
                writer.close();
                System.out.println(advice);
            }
        }catch(IOException ex){
            ex.printStackTrace();
        }
    }
    
    private String getAdvice(){
        //返回一条建议
    }
    
    
    public static void main(String[] args){
        DailyAdviceServer server = new DailyAdviceServer();
      	server.go();
    }
}

8.4 多线程

上面服务器代码有一个非常严重的问题——它一次只能服务一个用户。

  • 在没有完成目前用户的响应程序循环之前,它无法回到循环的开始出来处理下一个请求(无法进入accept()的等待来建立Socket给新的用户)
  • 想要服务器能够同时处理多个用户——需要使用多个线程。

线程说明:

  • 线程(thread)是独立的线程,代表一个独立的执行空间(stack);

  • 每个Java应用程序会启动一个主线程——将main()放在它自己执行空间的最开始处。Java虚拟机会负责主线程的启动(以及比如垃圾收集所需的系统用线程)。

  • 除非计算机有多个处理器,否则Java上新的线程其实不会是运行在操作系统上独立的进程

  • Java的multithreading

    • //建立新的线程来执行
      Thread t = new Thread();
      t.start();
      
    • 但是该线程并没执行任何程序,没有线程的任务——也就是独立线程要跑的程序代码。

  • java.lang.Thread

    • java.lang是默认就被import的,它是包括String、System等语言本身的基础的。
    • Thread是个表示线程的类,有启动start、连接join和让线程闲置sleep的方法等。
  • 多个执行空间

    • 看起来像是有好几件事同时发生;
    • 执行动作可以在执行空间非常快速的来回交换;
    • Java也只是在低层操作系统上执行的进程;
    • 轮到Java执行时,Java虚拟机中目前执行空间最上面的会被执行。
    • 线程要记录的一项事物就是目前线程执行空间做到哪里

线程的任务:

  • Runnable对象(线程的任务)

    • Runnable是个接口;
    • 在创建Thread对象时,传入Runnable对象;
    • 启动Thread,当调用线程的start()方法后,新线程会把Runnale对象的方法摆到新的执行空间中。
    • Runnable带有会放在执行空间的第一项方法run()
  • Thread对象需要任务,任务是线程启动时去执行的工作。该任务是新线程空间上的第一个方法,长得如下:

    • public void run(){
      	//会被新线程执行的代码
      }
      
    • Runnable定义了一个协议,它是一个接口,只有上面所述的一个方法。

    • 线程的任务可以被定义在任何实现Runnable的类上。线程只在乎传入给Thread的构造函数的参数是否为实现Runnable的类

    • Runnable传给Thread的构造函数时,实际上就是再给Thread取得run()的方法。

  • 实现Runnable接口举例:

    • public class MyRunnable implements Runnable{
          public void run(){
              //线程要实现的任务
          }
      }
      
      class ThreadTester{
          public static void main(String[] args){
              Runnable threadJob = new MyRunnable();
              Thread myThread = new Thread(threadJob);
              
              myThread.start();
              //调用start才会让线程开始执行,在此之前,他只是个Thread的实例,并不是真正的线程
              
              System.out.println("back in main");
          }
      }
      
  • Thread对象不能重复使用,即一旦线程的run()方法完成之后,该线程就不能再重新启动。过了改点后,线程就凉凉。Thread对象可能还待在堆上,还能接受某些方法的调用,但已经永远地失去了线程的可执行性,只剩下对象本身。

  • Thread对象是可以命名的:

    • setName()getName();
    • Thread.currentThread()用来表示当前正在执行的线程。

线程的状态:

  • 新建

    • Thread t = new Thread(r);
    • Thread的实例已经创建,但还没有启动,即有Thread对象,没有执行中线程。
  • 可执行

    • t.start();
    • 当启动线程时,它会变成可执行状态,此时该线程已经布置好执行空间。
  • 执行中

    • 依靠Java虚拟机中的线程调度机制来决定。
  • 一旦线程进入可执行状态,它会在可执行执行中两种状态中来来去去。但也存在另外一种状态:暂时不可执行(又称为被堵塞状态)

  • 线程调度器(scheduler)

    • 没有API可以调用调度器,调度无法确定;
    • 不能让程序依靠调度的特定行为来保持执行的正确性;
    • sleep,让线程去睡个几毫秒,才能让所有线程都有机会被执行。
      • sleep(),传入ms指定的时间,可以让程序更加可预测;
      • 但这个方法可能会抛出InterruptedException异常,必须包含在try-catch块中。
      • 这个异常其实是API用来支持线程间通信的机制,但实际没人这么做。
    • 单处理器的机器只能有一个执行中的线程。

另一种创建线程的方法:

  • Thread的子类来覆盖掉run()这个方法,然后调用Thread的无参构造函数来创建出新的线程。
    • Thread t = new Thread();
  • 这是另外一种创建线程的方法,是以面向对象的观点来建立的。将Thread做个子类来覆盖掉run()是完全合法的。

线程的常用API:

  • start():开启一个线程、

  • Thread.sleep(): 休眠一个线程

  • join():等待线程执行结束

    • worker1.join():主线程会等待worker1执行完成后执行
    • join可以加最大等待时间作参数
  • interrupt():从休眠中中断线程

    • 只能中断睡眠的线程
  • setDaemon():将线程设置为守护线程。当只剩下守护线程时,程序自动退出

8.5 并发性

线程会造成并发性的问题。

并发性(concurrency)问题会引发竞争状态,竞争状态会引发数据的损毁。

这一切都来自于一种状况:两个或以上线程存取单一对象的数据,也就是说两个不同执行空间上的方法都在堆上对同一个对象执行gettersetter

锁机制:

  • 确保线程一旦进入某方法后,就必须要能够在其他线程进入之前把任务执行完毕,让该方法跑起来像个原子;

  • 使用synchronized这个关键词来修饰方法使它每次只能被单一线程存取

    • synchronized关键词代表线程需要一把钥匙来存取被同步化(synchronized)过的线程。
    • 要保护数据,就把作用在数据上的方法给同步化。
  • 对象的锁:

    • 每个Java对象都有一个锁,每个锁只有一把钥匙;通常对象没上锁。
    • 对象的锁只会在有同步化的方法上起作用;当对象有一个或多个同步化方法时,线程只有在取得对象锁的钥匙时,才能进入同步化的方法。
    • 锁住的是存取数据的方法
    • 当线程开始执行并遇上有同步化的方法时,会发生:
      • 线程会认识到它需要对象的钥匙才能进入该方法。它会取得钥匙(这是由Java虚拟机处理的)。
      • 当线程持有钥匙时,没有其他线程可以进入该对象的同步化方法,因为每个对象只有一个钥匙。
  • 不把全部东西同步化的原因:

    • 同步化的方法有额外的成本;

    • 同步化方法会因为要同步并行的问题而慢下来;

    • 可能导致死锁

    • 原则上,最好只做最少量的同步化。理论上,同步化规模可以小于方法。

      • public void go(){
            doStuff();
            
            synchronized(this){
                criticalStuff();
                moreCriticalStuff();
            }
        }
        
  • 死锁:

    • 两个线程互相持有对方正在等待的东西;
    • Java没有数据库的事务回滚机制;
    • Java没有处理死锁的机制,甚至不知道死锁的发生。
  • 静态变量:

    • 每个被载入的类也有锁;
    • 当要对静态的方法做同步化是,Java会使用类本身的锁;
    • 如果同一个类有两个被同步化过的静态方法,则线程需要取得类的锁才能进入这些方法。

线程之间存在优先级,用来控制调度。

Synchronized是一个语法糖。

  • 将Synchronized加到代码块上

    • 继承线程的写法

    • class Count {
          public int cnt = 0;
      }
      
      class Worker extends Thread {
          public final Count count;
      
          public Worker(Count count) {
              this.count = count;
          }
      
          @Override
          public void run() {
              synchronized (count) {
                  for (int i = 0; i < 100000; i ++ ) {
                      count.cnt ++ ;
                  }
              }
          }
      }
      
      public class Main {
          public static void main(String[] args) throws InterruptedException {
              Count count = new Count();
      
              Worker worker1 = new Worker(count);
              Worker worker2 = new Worker(count);
      
              worker1.start();
              worker2.start();
              worker1.join();
              worker2.join();
      
              System.out.println(count.cnt);
          }
      }
      
  • 将Synchronized加到函数上(锁加到了this对象上)

    • 实现Runnable接口的方法

    • class Worker implements Runnable {
          public static int cnt = 0;
      
          private synchronized void work() {
              for (int i = 0; i < 100000; i ++ ) {
                  cnt ++ ;
              }
          }
      
          @Override
          public void run() {
              work();
          }
      }
      
      public class Main {
          public static void main(String[] args) throws InterruptedException {
              Worker worker = new Worker();
              Thread worker1 = new Thread(worker);
              Thread worker2 = new Thread(worker);
      
              worker1.start();
              worker2.start();
              worker1.join();
              worker2.join();
      
              System.out.println(Worker.cnt);
          }
      }
      

8.6 锁

  • lock:获取锁,如果锁已经被其他线程获取,则阻塞
  • unlock:释放锁,并唤醒被该锁阻塞的其他线程
import java.util.concurrent.locks.ReentrantLock;
class Worker extends Thread {
    public static int cnt = 0;
    private static final ReentrantLock lock = new ReentrantLock();


    @Override
    public void run() {
        for (int i = 0; i < 100000; i ++ ) {
            lock.lock();
            try {
                cnt ++ ;
            } finally {
                lock.unlock();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Worker worker1 = new Worker();
        Worker worker2 = new Worker();

        worker1.start();
        worker2.start();
        worker1.join();
        worker2.join();

        System.out.println(Worker.cnt);
    }
}

waitnotify

  • 只能用在synchronized的代码中;
package org.yxc;

class Worker extends Thread {
    private final Object object;
    private final boolean needWait;

    public Worker(Object object, boolean needWait) {
        this.object = object;
        this.needWait = needWait;
    }

    @Override
    public void run() {
        synchronized (object) {
            try {
                if (needWait) {
                    object.wait();
                    //另外线程执行结束后,被唤醒
                    System.out.println(this.getName() + ": 被唤醒啦!");
                } else {
                    object.notifyAll();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        for (int i = 0; i < 5; i ++ ) {
            Worker worker = new Worker(object, true);
            worker.setName("thread-" + i);
            worker.start();
        }

        Worker worker = new Worker(object, false);
        worker.setName("thread-" + 5);
        Thread.sleep(1000);
        worker.start();
    }
}

标签:Socket,Thread,Worker,网络,线程,new,多线程,public
From: https://www.cnblogs.com/whxky/p/16964947.html

相关文章

  • windwos的网络命令,经常用到!!
    网络安全|九个常用的网络命令,很实用!计算科学与信息化 2022-12-0711:47 发表于重庆收录于合集#计算机网络124个计算科学与信息化知识与生活15篇原创......
  • 网络垃圾小说的套路、问题三观
    网络垃圾小说的套路、问题三观:(0)大规模的各种抄袭,男主角都是从各种穿越过来的,其实早就不是原来那个人了,如果当入赘女婿一辈子都必须不能被看得起来;(1)随随便便遇到的女人都......
  • 计算机网络--网络层-中
    如何获取IP地址?(一个小问题)答案:静态和动态获取。静态配置:直接配置IP地址和子网掩码默认网关:某个路由器和该子网相连的接口DHCP协议:动态获取......
  • 最近沉迷Redis网络模型,无法自拔!终于知道Redis为啥这么快了
    1.用户空间和内核态空间1.1为什么要区分用户和内核服务器大多都采用Linux系统,这里我们以Linux为例来讲解:ubuntu和Centos都是Linux的发行版,发行版可以看成对......
  • 计算机网络--网络层-上
    网络层的核心功能:转发和路由数据报网络,网络无连接服务特点:每个分组携带目的地址,每个分组独立选路,根据聚合地址寻找目的地址 虚电路网络,网络连接服务......
  • MNIST-BP神经网络 & AdaBoost——框架解决
    MNISTTHEMNISTDATABASEofhandwrittendigitsBP神经网络模型训练importosimportnumpyasnpimporttorchimporttorch.utils.data#数据读取包importmatpl......
  • java面试(多线程)
    1. Callable、Future、FutureTash详解Callable与Future是在JAVA的后续版本中引入进来的,Callable类似于Runnable接口,实现Callable接口的类与实现Runnable的类都是可以被线程......
  • JUC5 多线程锁(下)
    1.​​synchronize​​锁升级:无锁,偏向锁,轻量锁,重量锁(看病:社区医院->三甲医院)1.1 概述按照获得锁和释放锁的性能消耗,锁的分类:1.无锁状态2.偏向锁:不进行​​CAS​​,测......
  • JUC4 多线程锁(上)
    1.乐观锁和悲观锁①.悲观锁什么是悲观锁?认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改适合写操作多的场......
  • ​Docker网络实现的基本原理
    Docker的网络实现基本原理是利用了Linux的网络命令空间和虚拟网络设备,因为Linux通过在内核中进行数据复制来实现虚拟接口之间的数据转发,即发送接口的发送缓存中的数据包将......