Java阶段一Day22

news/2024/5/21 23:57:25/文章来源:https://blog.csdn.net/weixin_65777087/article/details/130139011

Java阶段一Day22

文章目录

  • Java阶段一Day22
    • 线程安全
      • synchronized
  • 教师总结
    • 新单词
    • 多线程
      • 多线程并发安全问题
        • 概念
        • synchronized关键字
          • 同步方法
          • 同步块
          • 在静态方法上使用synchronized
        • 互斥锁
      • 总结
        • 重点:多线程并发安全问题
    • 聊天室(续)
        • 实现服务端发送消息给客户端
      • 服务端转发消息给所有客户端
        • 客户端解决收发消息的冲突问题
    • 服务端解决多线程并发安全问题

线程安全

synchronized

  • 当一个方法被关键字synchronized修饰后,该方法称为同步方法
  • 即多个线程不能同时在方法内部执行
  • 将多个线程并发操作改为有先后顺序的同步执行就可以有效的解决多线程并发安全问题
  • 但同时也会牺牲效率,所以想要有效缩小范围

教师总结

新单词

  • synchronized 同步

多线程

多线程并发安全问题

概念

当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成.

当beans为1时,若两个线程同时调用getBean方法,t1线程先进行if判断,此时beans不为0,于是执行if后面的操作准备获取beans的值并对其进行–操作,但是还没有执行这句话发生了线程切换,那么t2线程也进行if判断,由于beans不为0,t2线程也执行if后面的操作获取beans的值并对其进行–操作,这会导致两个线程最终将beans的值从-减为了-1.导致后续操作出现死循环。

这就是由于线程切换不确定导致执行顺序出现了混乱,也就是所谓的并发安全问题

package thread;/*** 多线程并发安全问题* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现* 混乱,严重时可能导致系统瘫痪。* 临界资源:同时只能被单一线程访问操作过程的资源。*/
public class SyncDemo {public static void main(String[] args) {Table table = new Table();Thread t1 = new Thread(){public void run(){while(true){int bean = table.getBean();Thread.yield();System.out.println(getName()+":"+bean);}}};Thread t2 = new Thread(){public void run(){while(true){int bean = table.getBean();/*static void yield()线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。*/Thread.yield();System.out.println(getName()+":"+bean);}}};t1.start();t2.start();}
}class Table{private int beans = 20;//桌子上有20个豆子public int getBean(){if(beans==0){throw new RuntimeException("没有豆子了!");}Thread.yield();return beans--;}
}

synchronized关键字

解决并发安全问题的本质就是将多个线程并发(同时)操作改为同步(排队)操作来解决。

synchronized有两种使用方式

  • 在方法上修饰,此时该方法变为一个同步方法
  • 同步块,可以更准确的锁定需要排队的代码片段
同步方法

当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时 在方法内部执行.只能有先后顺序的一个一个进行. 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.

package thread;/*** 多线程并发安全问题* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现* 混乱,严重时可能导致系统瘫痪。* 临界资源:同时只能被单一线程访问操作过程的资源。*/
public class SyncDemo {public static void main(String[] args) {Table table = new Table();Thread t1 = new Thread(){public void run(){while(true){int bean = table.getBean();Thread.yield();System.out.println(getName()+":"+bean);}}};Thread t2 = new Thread(){public void run(){while(true){int bean = table.getBean();/*static void yield()线程提供的这个静态方法作用是让执行该方法的线程主动放弃本次时间片。这里使用它的目的是模拟执行到这里CPU没有时间了,发生线程切换,来看并发安全问题的产生。*/Thread.yield();System.out.println(getName()+":"+bean);}}};t1.start();t2.start();}
}class Table{private int beans = 20;//桌子上有20个豆子/*** 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能* 同时执行该方法。* 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发* 安全问题。* 相当于让多个线程从原来的抢着操作改为排队操作。*/public synchronized int getBean(){if(beans==0){throw new RuntimeException("没有豆子了!");}Thread.yield();return beans--;}
}
同步块

有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段.

语法:

synchronized(同步监视器对象){需要多线程同步执行的代码片段
}

同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个.

package thread;/*** 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率。** 同步块* 语法:* synchronized(同步监视器对象){*     需要多个线程同步执行的代码片段* }* 同步块可以更准确的锁定需要多个线程同步执行的代码片段来有效缩小排队范围。*/
public class SyncDemo2 {public static void main(String[] args) {Shop shop = new Shop();Thread t1 = new Thread(){public void run(){shop.buy();}};Thread t2 = new Thread(){public void run(){shop.buy();}};t1.start();t2.start();}
}class Shop{public void buy(){/*在方法上使用synchronized,那么同步监视器对象就是this。*/
//    public synchronized void buy(){Thread t = Thread.currentThread();//获取运行该方法的线程try {System.out.println(t.getName()+":正在挑衣服...");Thread.sleep(5000);/*使用同步块需要指定同步监视器对象,即:上锁的对象这个对象可以是java中任何引用类型的实例,只要保证多个需要排队执行该同步块中代码的线程看到的该对象是"同一个"即可*/synchronized (this) {
//            synchronized (new Object()) {//没有效果!System.out.println(t.getName() + ":正在试衣服...");Thread.sleep(5000);}System.out.println(t.getName()+":结账离开");} catch (InterruptedException e) {e.printStackTrace();}}
}
在静态方法上使用synchronized

当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.

静态方法使用的同步监视器对象为当前类的类对象(Class的实例).

注:类对象会在后期反射知识点介绍.

package thread;/*** 静态方法上如果使用synchronized,则该方法一定具有同步效果。*/
public class SyncDemo3 {public static void main(String[] args) {Thread t1 = new Thread(){public void run(){Boo.dosome();}};Thread t2 = new Thread(){public void run(){Boo.dosome();}};t1.start();t2.start();}
}
class Boo{/*** synchronized在静态方法上使用是,指定的同步监视器对象为当前类的类对象。* 即:Class实例。* 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射* 知识点的时候会介绍类对象。*/public synchronized static void dosome(){       try {Thread t = Thread.currentThread();System.out.println(t.getName() + ":正在执行dosome方法...");Thread.sleep(5000);System.out.println(t.getName() + ":执行dosome方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}}
}

静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象

package thread;public class SyncDemo3 {public static void main(String[] args) {
//        new Thread(()->Foo.dosome()).start();
//        new Thread(Foo::dosome).start();Foo f1 = new Foo();Foo f2 = new Foo();new Thread(()->f1.dosome()).start();new Thread(()->f2.dosome()).start();}
}class Foo{
//    public synchronized static void dosome(){public static void dosome(){/*在静态方法中使用同步块时,同步监视器对象还是使用当前类的类对象获取类对象的方式:类名.class例如获取Foo的类对象就是:Foo.class*/synchronized (Foo.class) {try {Thread t = Thread.currentThread();System.out.println(t.getName() + ":正在执行dosome方法");Thread.sleep(5000);System.out.println(t.getName() + ":执行dosome方法完毕");} catch (InterruptedException e) {e.printStackTrace();}}}
}

互斥锁

当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.

使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.

package thread;/*** 互斥锁* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。*/
public class SyncDemo4 {public static void main(String[] args) {Foo foo = new Foo();Thread t1 = new Thread(){public void run(){foo.methodA();}};Thread t2 = new Thread(){public void run(){foo.methodB();}};t1.start();t2.start();}
}
class Foo{public synchronized void methodA(){Thread t = Thread.currentThread();try {System.out.println(t.getName()+":正在执行A方法...");Thread.sleep(5000);System.out.println(t.getName()+":执行A方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}public synchronized void methodB(){Thread t = Thread.currentThread();try {System.out.println(t.getName()+":正在执行B方法...");Thread.sleep(5000);System.out.println(t.getName()+":执行B方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}
}

总结

守护线程与普通线程的区别:守护线程是通过普通线程调用setDaemon(true)设置而来的

主要区别体现在当java进程中所有的普通线程都结束时进程会结束,在结束前会杀死所有还在运行的守护线程。

重点:多线程并发安全问题

  • 什么是多线程并发安全问题:

    当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致执行顺序出现混乱。

    解决办法:

    将并发操作改为同步操作就可有效的解决多线程并发安全问题

  • 同步与异步的概念:同步和异步都是说的多线程的执行方式。

    多线程各自执行各自的就是异步执行,而多线程执行出现了先后顺序进行就是同步执行

  • synchronized的两种用法

    1.直接在方法上声明,此时该方法称为同步方法,同步方法同时只能被一个线程执行

    2.同步块,推荐使用。同步块可以更准确的控制需要同步执行的代码片段。

    有效的缩小同步范围可以在保证并发安全的前提下提高并发效率

  • 同步监视器对象的选取:

    对于同步的成员方法而言,同步监视器对象不可指定,只能是this

    对于同步的静态方法而言,同步监视器对象也不可指定,只能是类对象

    对于同步块而言,需要自行指定同步监视器对象,选取原则:

    1.必须是引用类型

    2.多个需要同步执行该同步块的线程看到的该对象必须是同一个

  • 互斥性

    当使用多个synchronized修饰了多个代码片段,并且指定的同步监视器都是同一个对象时,这些代码片段就是互斥的,多个线程不能同时在这些代码片段上执行。

聊天室(续)

实现服务端发送消息给客户端

在服务端通过Socket获取输出流,客户端获取输入流,实现服务端将消息发送给客户端.

这里让服务端直接将客户端发送过来的消息再回复给客户端来进行测试.

服务端代码:

package socket;import java.io.*;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
import java.net.Socket;/*** 聊天室服务端*/
public class Server {/*** 运行在服务端的ServerSocket主要完成两个工作:* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket*   就可以和该客户端交互了** 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个* 电话使得服务端与你沟通。*/private ServerSocket serverSocket;/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");/*实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他应用程序占用的端口相同,否则会抛出异常:java.net.BindException:address already in use端口是一个数字,取值范围:0-65535之间。6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。*/serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {while(true) {System.out.println("等待客户端链接...");/*ServerSocket提供了接受客户端链接的方法:Socket accept()这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例通过这个Socket就可以与客户端进行交互了。可以理解为此操作是接电话,电话没响时就一直等。*/Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");//启动一个线程与该客户端交互ClientHandler clientHandler = new ClientHandler(socket);Thread t = new Thread(clientHandler);t.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}/*** 定义线程任务* 目的是让一个线程完成与特定客户端的交互工作*/private class ClientHandler implements Runnable{private Socket socket;private String host;//记录客户端的IP地址信息public ClientHandler(Socket socket){this.socket = socket;//通过socket获取远端计算机地址信息host = socket.getInetAddress().getHostAddress();}public void run(){try{/*Socket提供的方法:InputStream getInputStream()获取的字节输入流读取的是对方计算机发送过来的字节*/InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw,true);String message = null;while ((message = br.readLine()) != null) {System.out.println(host + "说:" + message);//将消息回复给客户端pw.println(host + "说:" + message);}}catch(IOException e){e.printStackTrace();}}}}

客户端代码:

package socket;import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;/*** 聊天室客户端*/
public class Client {/*java.net.Socket 套接字Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成与远端计算机的数据交互工作。我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克风(输出流),通过它们就可以与对方交流了。*/private Socket socket;/*** 构造方法,用来初始化客户端*/public Client(){try {System.out.println("正在链接服务端...");/*实例化Socket时要传入两个参数参数1:服务端的地址信息可以是IP地址,如果链接本机可以写"localhost"参数2:服务端开启的服务端口我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上的服务端应用程序。实例化的过程就是链接的过程,如果链接失败会抛出异常:java.net.ConnectException: Connection refused: connect*/socket = new Socket("localhost",8088);System.out.println("与服务端建立链接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start(){try {/*Socket提供了一个方法:OutputStream getOutputStream()该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。*///低级流,将字节通过网络发送给对方OutputStream out = socket.getOutputStream();//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);//高级流,负责块写文本数据加速BufferedWriter bw = new BufferedWriter(osw);//高级流,负责按行写出字符串,自动行刷新PrintWriter pw = new PrintWriter(bw,true);//通过socket获取输入流读取服务端发送过来的消息InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);Scanner scanner = new Scanner(System.in);while(true) {String line = scanner.nextLine();if("exit".equalsIgnoreCase(line)){break;}pw.println(line);line = br.readLine();System.out.println(line);}} catch (IOException e) {e.printStackTrace();} finally {try {/*通讯完毕后调用socket的close方法。该方法会给对方发送断开信号。*/socket.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {Client client = new Client();client.start();}
}

服务端转发消息给所有客户端

当一个客户端发送一个消息后,服务端收到后如何转发给所有客户端.

问题:例如红色的线程一收到客户端消息后如何获取到橙色的线程二中的输出流?得不到就无法将消息转发给橙色的客户端(进一步延伸就是无法转发给所有其他客户端)

解决:内部类可以访问外部类的成员,因此在Server类上定义一个集合allOut可以被所有内部类ClientHandler实例访问.从而将这些ClientHandler实例之间想互访的数据存放在这个集合中达到共享数据的目的.对此只需要将所有ClientHandler中的输出流都存入到集合allOut中就可以达到互访输出流转发消息的目的了.

在这里插入图片描述

服务端代码:

package socket;import java.io.*;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
import java.net.Socket;
import java.util.List;
import java.util.ArrayList;/*** 聊天室服务端*/
public class Server {/*** 运行在服务端的ServerSocket主要完成两个工作:* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket*   就可以和该客户端交互了** 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个* 电话使得服务端与你沟通。*/private ServerSocket serverSocket;/*存放所有客户端输出流,用于广播消息*/private List<PrintWriter> allOut = new ArrayList();/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");/*实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他应用程序占用的端口相同,否则会抛出异常:java.net.BindException:address already in use端口是一个数字,取值范围:0-65535之间。6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。*/serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {while(true) {System.out.println("等待客户端链接...");/*ServerSocket提供了接受客户端链接的方法:Socket accept()这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例通过这个Socket就可以与客户端进行交互了。可以理解为此操作是接电话,电话没响时就一直等。*/Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");//启动一个线程与该客户端交互ClientHandler clientHandler = new ClientHandler(socket);Thread t = new Thread(clientHandler);t.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}/*** 定义线程任务* 目的是让一个线程完成与特定客户端的交互工作*/private class ClientHandler implements Runnable{private Socket socket;private String host;//记录客户端的IP地址信息public ClientHandler(Socket socket){this.socket = socket;//通过socket获取远端计算机地址信息host = socket.getInetAddress().getHostAddress();}public void run(){try{/*Socket提供的方法:InputStream getInputStream()获取的字节输入流读取的是对方计算机发送过来的字节*/InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);BufferedWriter bw = new BufferedWriter(osw);PrintWriter pw = new PrintWriter(bw,true);//将该输出流存入allOut中//1对allOut数组扩容allOut.add(pw);String message = null;while ((message = br.readLine()) != null) {System.out.println(host + "说:" + message);//将消息回复给所有客户端for(PrintWriter o : allOut) {o.println(host + "说:" + message);}}}catch(IOException e){e.printStackTrace();}}}}

客户端解决收发消息的冲突问题

由于客户端start方法中循环进行的操作顺序是先通过控制台输入一句话后将其发送给服务端,然后再读取服务端发送回来的一句话.这导致如果客户端不输入内容就无法收到服务端发送过来的其他信息(其他客户端的聊天内容).因此要将客户端中接收消息的工作移动到一个单独的线程上执行,才能保证收发消息互不打扰.

客户端代码:

package socket;import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;/*** 聊天室客户端*/
public class Client {/*java.net.Socket 套接字Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成与远端计算机的数据交互工作。我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克风(输出流),通过它们就可以与对方交流了。*/private Socket socket;/*** 构造方法,用来初始化客户端*/public Client(){try {System.out.println("正在链接服务端...");/*实例化Socket时要传入两个参数参数1:服务端的地址信息可以是IP地址,如果链接本机可以写"localhost"参数2:服务端开启的服务端口我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上的服务端应用程序。实例化的过程就是链接的过程,如果链接失败会抛出异常:java.net.ConnectException: Connection refused: connect*/socket = new Socket("localhost",8088);System.out.println("与服务端建立链接!");} catch (IOException e) {e.printStackTrace();}}/*** 客户端开始工作的方法*/public void start(){try {//启动读取服务端发送过来消息的线程ServerHandler handler = new ServerHandler();Thread t = new Thread(handler);t.setDaemon(true);t.start();/*Socket提供了一个方法:OutputStream getOutputStream()该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。*///低级流,将字节通过网络发送给对方OutputStream out = socket.getOutputStream();//高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);//高级流,负责块写文本数据加速BufferedWriter bw = new BufferedWriter(osw);//高级流,负责按行写出字符串,自动行刷新PrintWriter pw = new PrintWriter(bw,true);Scanner scanner = new Scanner(System.in);while(true) {String line = scanner.nextLine();if("exit".equalsIgnoreCase(line)){break;}pw.println(line);}} catch (IOException e) {e.printStackTrace();} finally {try {/*通讯完毕后调用socket的close方法。该方法会给对方发送断开信号。*/socket.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {Client client = new Client();client.start();}/*** 该线程负责接收服务端发送过来的消息*/private class ServerHandler implements Runnable{public void run(){//通过socket获取输入流读取服务端发送过来的消息try {InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);String line;//循环读取服务端发送过来的每一行字符串while((line = br.readLine())!=null){System.out.println(line);}} catch (IOException e) {e.printStackTrace();}}}
}

服务端解决多线程并发安全问题

为了让能叫消息转发给所有客户端,我们 在Server上添加了一个集合类型的属性allOut,并且共所有线程ClientHandler使用,这时对集合的操作要考虑并发安全问题,还要考虑对集合的不同操作之间的互斥问题。因此,对allOut集合的添加元素,删除元素和遍历操作要进行互斥。

最终代码:

package socket;import java.io.*;
import java.net.ServerSocket;
import java.nio.charset.StandardCharsets;
import java.net.Socket;/*** 聊天室服务端*/
public class Server {/*** 运行在服务端的ServerSocket主要完成两个工作:* 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接* 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket*   就可以和该客户端交互了** 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个* 电话使得服务端与你沟通。*/private ServerSocket serverSocket;/*存放所有客户端输出流,用于广播消息*/private PrintWriter[] allOut = {};/*** 服务端构造方法,用来初始化*/public Server(){try {System.out.println("正在启动服务端...");/*实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他应用程序占用的端口相同,否则会抛出异常:java.net.BindException:address already in use端口是一个数字,取值范围:0-65535之间。6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。*/serverSocket = new ServerSocket(8088);System.out.println("服务端启动完毕!");} catch (IOException e) {e.printStackTrace();}}/*** 服务端开始工作的方法*/public void start(){try {while(true) {System.out.println("等待客户端链接...");/*ServerSocket提供了接受客户端链接的方法:Socket accept()这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例通过这个Socket就可以与客户端进行交互了。可以理解为此操作是接电话,电话没响时就一直等。*/Socket socket = serverSocket.accept();System.out.println("一个客户端链接了!");//启动一个线程与该客户端交互ClientHandler clientHandler = new ClientHandler(socket);Thread t = new Thread(clientHandler);t.start();}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) {Server server = new Server();server.start();}/*** 定义线程任务* 目的是让一个线程完成与特定客户端的交互工作*/private class ClientHandler implements Runnable{private Socket socket;private String host;//记录客户端的IP地址信息public ClientHandler(Socket socket){this.socket = socket;//通过socket获取远端计算机地址信息host = socket.getInetAddress().getHostAddress();}public void run(){PrintWriter pw = null;try{/*Socket提供的方法:InputStream getInputStream()获取的字节输入流读取的是对方计算机发送过来的字节*/InputStream in = socket.getInputStream();InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isr);OutputStream out = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(out,StandardCharsets.UTF_8);BufferedWriter bw = new BufferedWriter(osw);pw = new PrintWriter(bw,true);//将该输出流存入allOut中synchronized (allOut) {allOut.add(pw);}//通知所有客户端该用户上线了System.out.println(host + "上线了,当前在线人数:"+allOut.length);String message = null;while ((message = br.readLine()) != null) {System.out.println(host + "说:" + message);//将消息回复给所有客户端synchronized (allOut) {for (PrintWriter o : allOut) {allOut[i].println(host + "说:" + message);}}}}catch(IOException e){e.printStackTrace();}finally{//处理客户端断开链接的操作//将当前客户端的输出流从allOut中删除synchronized (allOut) {allOut.remove(pw);}System.out.println(host+"下线了,当前在线人数:"+allOut.length);try {socket.close();//与客户端断开链接} catch (IOException e) {e.printStackTrace();}}}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android FrameWork 知识点与面试题整合~

1.如何对 Android 应用进行性能分析 android 性能主要之响应速度 和UI刷新速度。 首先从函数的耗时来说&#xff0c;有一个工具TraceView 这是androidsdk自带的工作&#xff0c;用于测量函数耗时的。 UI布局的分析&#xff0c;可以有2块&#xff0c;一块就是Hierarchy Viewe…

SpringBoot集成Mybatis-Plus实现多租户动态数据源

1. 概述 最近接手一个多租户系统&#xff0c;多租户主要的就是租户之间的数据是相互隔离的&#xff0c;每个租户拥有自己独立的数据&#xff0c;相互之间不干扰。目前实现多租户主要有三种方案&#xff1a; 独立数据库 每个租户拥有自己单独的数据库&#xff0c;从物理上隔离了…

手写一个IO泄露监测框架

作者&#xff1a;长安皈故里 大家好&#xff0c;最近由于项目原因&#xff0c;对IO资源泄漏的监测进行了一番调研深入了解&#xff0c;发现IO泄漏监测框架实现成本比较低&#xff0c;效果很显著&#xff1b;同时由于IO监测涉及到反射&#xff0c;还了解到了通过一种巧妙的方式实…

通达信欧奈尔RPS指标公式详解

RPS相对强度指标&#xff0c;是国内的投资者根据威廉欧奈尔所著书籍《笑傲股市》中的RS评级改进的。 根据书中介绍&#xff1a; RS评级衡量了某一给定股票在过去52周内相对股市中其他股票的表现。市场上每一只股票都被指定了1~99范围内的某一数值&#xff0c;99代表相对强度最高…

YOLOV7运行步骤(推理、训练全过程)

下载源代码&#xff1a;点击下载 执行以下命令安装requirements.txt中的相关依赖 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple官网下载权重yolov7.pt&#xff08;测试使用&#xff09;、yolov7-tiny.pt&#xff08;训练使用&#xff0c;这里…

10 JS01——初识JS

目标&#xff1a; 1、初识JavaScript 2、JavaScript注释 3、JavaScript输入输出语句 一、初识JavaScript 1、JavaScript是什么 JavaScript是世界上最流行的语言之一&#xff0c;是一种运行在客户端的脚本语言(Script是脚本的意思) 脚本语言:不需要编译&#xff0c;运行过程…

Vue2-黑马(九)

0目录&#xff1a; &#xff08;1&#xff09;router-动态菜单 &#xff08;2&#xff09;vuex-入门 &#xff08;3&#xff09;vuex-mapState &#xff08;1&#xff09;router-动态菜单 我们点击按钮跳转到主页面&#xff0c;主页在制作动态菜单&#xff0c;路由的跳转方…

keil代码格式化快捷方法

美化当前文件&#xff1a;-n !E --styleansi -p -s4 -S -f -xW -w -xw 美化整个工程文件&#xff1a;-n "$E*.c" "$E*.h" --styleansi -p -s4 -S -f -xW -w -xw -R 当前时间&#xff1a;!E~E^E 添加文件注释&#xff1a;!E 函数功能注释&#xff1a;!E ~…

快排(动图详细版,快速理解)

注&#xff1a;本文主要介绍六大排序中的快排 文章目录前言一、三大法则1.1 Hoare法1.2 挖坑法1.3 双指针法&#xff08;更加便捷&#xff09;1.4 三种方法时间复杂度计算二、快排栈问题优化方式2.1 三数取中2.2 小区间优化三、非递归快排前言 快速排序是Hoare于1962年提出的一…

Linux高并发服务器(webserver)

一.有限状态机 它的转移函数表示系统从一个状态转移到另一个状态的条件 二.EPOLL 在内核中创建一个数据&#xff0c;这个数据有两个比较重要的数据&#xff0c;一个是需要检测的文件描述符的信息&#xff08;红黑树&#xff09;&#xff0c;一个双向链表&#xff0c;存放检测到…

利用多专家模型解决长尾识别任务

来源&#xff1a;投稿 作者&#xff1a;TransforMe 编辑&#xff1a;学姐 贡献 提出了RoutIng Diverse Experts&#xff08;RIDE&#xff09;&#xff0c;不仅可以减少所有类别的variance&#xff0c;并且还可以减少尾部类的bias。同时提升了头部和尾部的性能。 思路 目前存…

easyrecovery2023电脑文件数据恢复软件功能介绍

EasyRecovery功能全面&#xff0c;即便是没有经验的小白用户也可以很快上手&#xff0c;让你足不出户即可搞定常见的数据丢失问题。 在使用和操作存储设备期间&#xff0c;数据丢失问题在所难免。比如&#xff0c;误删除某个文件、不小心将有数据的分区格式化、误清空了有重要…

2023“认证杯”数学中国数学建模赛题浅析

2023年认证杯”数学中国数学建模如期开赛&#xff0c;本次比赛与妈杯&#xff0c;泰迪杯时间有点冲突。因此&#xff0c;个人精力有限&#xff0c;有些不可避免地错误欢迎大家指出。为了大家更方便的选题&#xff0c;我将为大家对四道题目进行简要的解析&#xff0c;以方便大家…

【vue3】04-vue基础语法补充及阶段案例

文章目录vue基础语法补充vue的computedvue的watch侦听书籍购物车案例vue基础语法补充 vue的computed computed&#xff1a;用于声明要在组件实例上暴露的计算属性。&#xff08;官方文档描述&#xff09; 我们已经知道&#xff0c;在模板中可以直接通过插值语法显示一些data中…

智能网卡相关知识(smart nic 、DPU)

网卡作为穿行在网络与计算之间的桥梁&#xff0c;是可以解决计算瓶颈的关键硬件。 随着CPU 密度和数据中心网络带宽的进一步提升&#xff0c;用户对预期性能的需求&#xff0c;系统运行平稳性都会有更高的要求。云厂商一方面面临巨大的成本压力&#xff0c;另一方面面临巨大的…

新一代异步IO框架 io_uring | 得物技术

1.Linux IO 模型分类 相比于kernel bypass 模式需要结合具体的硬件支撑来讲&#xff0c;native IO是日常工作中接触到比较多的一种&#xff0c;其中同步IO在较长一段时间内被广泛使用&#xff0c;通常我们接触到的IO操作主要分为网络IO和存储IO。在大流量高并发的今天&#xff…

光伏电池片技术N型迭代,机器视觉检测赋能完成产量“弯道超车”

电池片是光伏发电的核心部件&#xff0c;其技术路线和工艺水平直接影响光伏组件的发电效率和使用寿命。随着硅料、硅片技术逐渐接近其升级迭代空间的瓶颈&#xff0c;电池片环节正处于技术变革期&#xff0c;是光伏产业链中迭代最快的部分。P型中PERC电池片是现阶段市场的主流产…

C/C++每日一练(20230413)

目录 1. 与浮点数A最接近的分数B/C &#x1f31f; 2. 比较版本号 &#x1f31f;&#x1f31f; 3. 无重复字符的最长子串 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每…

Multi-modal Alignment using Representation Codebook

Multi-modal Alignment using Representation Codebook 题目Multi-modal Alignment using Representation Codebook译题使用表示代码集的多模态对齐期刊/会议CVPR 摘要&#xff1a;对齐来自不同模态的信号是视觉语言表征学习&#xff08;representation learning&#xff09;的…

Vue2_02_指令

模板语法 — Vue.js (vuejs.org) 指令 (Directives) 是带有 v- 前缀的特殊 attribute 参数 一些指令能够接收一个“参数”&#xff0c;在指令名称之后以冒号表示 <a v-bind:href"url">...</a> 动态参数 可以用方括号括起来的 JavaScript 表达式作为一…