JAVA-SE下:多线程,Socket与XML解析

xiaoxiao2021-02-28  56

#多线程基础 ##概念

进程(process):进程就是一块包含了某些资源的内存区域。当操作系统创建一个进程后,该进程会自动申请一个主线程。进程是并发运行的。线程(thread):进程中包含的一个或多个执行单元称为线程。一个线程是进程的一个顺序执行流。同类的多个线程共享内存空间和系统资源。线程在切换时负荷小区别意义:进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。多线程的意义在于:一个应用程序中,有多个部分可以同时执行。线程使用的场合 线程常用于在一个程序中需要同时完成多个任务的情况。我们可以将每个任务定义为一个线程,使他们可以一同工作。例如我们在玩某个游戏时,这个游戏由操作系统运行,所以其运行在一个独立的进程中,而在游戏中我们会听到某些背景音乐,某个角色在移动,出现某些绚丽的动画效果等,这些在游戏中都是同时发生的,但实际上,播放音乐是在一个线程中独立完成的,移动某个角色,播放某些特效也都是在独立的线程中完成的。并发:宏观上同时运行而微观上走走停停的现象称为并发。线程状态:New:当我们创建一个线程时,该线程并没有纳入线程调度,其处于一个new状态。Runnable:当调用线程的start方法后,该线程纳入线程调度的控制,其处于一个可运行状态,等待分配时间片段以并发运行。Running:当该线程被分配到了时间片段后其被CPU运行,这是该线程处于running状态。Blocked:线程在运行过程中可能会出现阻塞现象,比如等待用户输入信息等。但阻塞状态不是百分百出现的,具体要看代码中是否有相关需求。(waiting(),sleep(),join())Dead:当线程的任务全部运行完毕,或在运行过程中抛出了一个未捕获的异常,那么线程结束,等待GC回收。 线程里是并发运行代码,两段代码间不存再先后运行的概念。同步执行:有先后顺序运行多段代码异步运行:各自执行各的,多线程是异步运行。 线程优先级:获取cpu的几率,线程的优先级有10个等级,分别用整数1-10表示。其中1最低,10最高,5为默认。

java中所有代码都是靠线程运行的,main方法也不例外,只不过运行main方法的线程不是由我们创建的。 一个进程是否结束,是看这个进程中是否有还在运行的前台线程。当所有前台线程结束,守护线程自动结束。 ##线程创建两种方式 java中的线程是由Thread的实例表示的。

Thread的创建有两种方式 1:继承Thread并重写run方法。 第一种创建线程的方式虽然定义简单,但也存在一些不足:由于java是单继承的,但在实际开发中,为了复用一个类的方法,我们需要继承那个类,但自身又希望是一个线程时导致的继承冲突。继承了线程需要重写run方法来定义该线程执行的任务代码,这就导致了线程与执行的任务有一个必然的耦合关系,不利于线程的重用。

2:实现Runnable接口并重写run方法来单独定义任务。

启动线程是调用线程的start方法,而不要直接调用run方法 start方法的作用是将该线程纳入线程调度。 一旦start当法执行完毕后,那么该线程的run方法很快被运行(只要获取了cpu时间片)

用匿名内部类创建线程的两种方式 //方式一 new Thread(){ public void run(){ for(int i=0;i<1000;i++){ System.out.println("你是谁啊?"); } } }.start(); //方式二 new Thread(new Runnable(){ @Override public void run() { for(int i=0;i<1000;i++){ System.out.println("我是谁谁谁?"); } } }).start();

##线程中常用方法

static Thread currentThread():Thread t=Thread.currentThread(); 获取运行这个方法的线程static void sleep(long ms):Thread.sleep(1000); 将运行当前方法的线程阻塞指定毫秒 void join():download.join(); 在一个线程中,加入另一个线程。 join可以协调线程间同步运行max.setPriority(Thread.MAX_PRIORITY):设置优先级static void yield() :暂停当前正在执行的线程对象,并执行其他线程。 获取线程信息的相关方法 long id=main.getId(); String name=main.getName(); int priority=main.getPriority(); boolean isAlive=main.isAlive(); boolean isDaemon=main.isDaemon(); boolean isInterrupted=main.isInterrupted();

当一个方法的局部内部类中需要引用该方法的其他局部变量的时,该变量必须是final的(保持里外的一致) jdk1.8之后由于内存问题被重新定义,不在有这个问题,所以就不再需要做上述设定

#synchronized关键字

多线程并发存在安全问题:当多个线程并发访问统一资源时,由于线程切换时机不缺定,导致代码未按照设计方式执行导致的逻辑混乱。严重时可能导致系统瘫痪。这里解决多线程并发安全的手段:将各干各的变为”排队执行“ 当一个方法被synchronized修饰后,那么该方法称为”同步方法“, 即:多个线程不能同时进入到方法内部执行。在方法上使用synchronized修饰后,上锁的对象就是当前方法所属对象, 静态方法使用synchronized,那么一定具有同步效果 静态方法上锁的对象是该方法所属类的类对象 * 实际上JVM在加载一个类的class文件时,会实例化一个Class类型的实例去保存该类的信息(属性,方法等)所以JVM中每个加载过的类都有且只有一个Class的实例用于表示它。这个Class实例就是该类的类对象 ##synchronized 块(同步块) 同步块可以更精确的控制需要同步执行的代码片段。有效缩小同步范围提高并发效率,但是需要注意,**同步块需要指定“同步监视器”即:上锁的对象,要保证需要同步运行该段代码的线程看到的该对象是同一个。**因次this可以为不可变对象–字符串“锁”。 synchronized (this) { System.out.println(t.getName()); Thread.sleep(5000); }

ArrayList,LinkedList,HashSet,HashMap都不是线程安全的

list=Collections.synchronizedList(list); set=Collections.synchronizedSet(set); map=Collections.synchronizedMap(map);

线程安全的集合也不与迭代器遍历该集合的操作互斥。 迭代器要求遍历的过程中不能通过集合的方式增删元素,否则会抛出异常,所有在多个线程间有这样的操作时,需要自行维护遍历集合与集合元素操作间的互斥关系。

线程池

线程池主要解决了两个问题: 1:控制线程数量。因为线程数多了,会导致内存开销大,严重时会导致系统瘫痪,并且由于线程数量多会导致cpu过度切换,拖慢系统响应。 2:重用线程 //创建固定大小的线程池(部分代码) ExecutorService threadpool=Executors.newFixedThreadPool(2); threadpool.execute(runn); System.out.println("指派了一个任务给线程池"); } threadpool.shutdownNow(); System.out.println("停止线程池!");

#网络通信之TCP与UDP ##TCP

即socket

客户端client //聊天室客户端 public class Client { //java.net.Socket 套接字,封装了TCP协议,使用它可以与远端计算机通讯。 private Socket socket; //构造方法,用来初始化客户端 public Client() throws Exception{ /* * 实例化Socket时需要传入两个参数: * 1:服务端计算机的地址信息(IP地址) * 2:服务端计算机上运行的服务端应用程序申请的 * 服务端口 * * 通过IP可以找到服务端的计算机,通过端口 * 可以连接到运行在服务端计算机上的服务端 * 应用程序。 * * 实例化Socket的过程就是连接服务端的过程 * 若服务端无响应,实例化过程会抛出异常 * * 端口号是一个整数,2字节内的整数0-65535 * 但3000以内的端口号不要使用,因为紧密的 * 绑定着系统程序,和世界上流行的应用程序。 * 10000以上也很少被使用。 */ try { socket = new Socket("localhost",8088); } catch (Exception e) { //记录日志。 throw e; } //客户端开始工作的方法 public void start(){ try { //用来获取用户输入 Scanner scanner = new Scanner(System.in); /* * Socket提供方法: * OutputStream getOutputStream() * 通过获取的输出流写出的数据就可以通过 * 网络发送给远端计算机,对于客户端而言 * 远端就是服务端。 */ OutputStream out = socket.getOutputStream(); OutputStreamWriter osw= new OutputStreamWriter(out,"UTF-8"); PrintWriter pw= new PrintWriter(osw,true); //接受客户端发送过来消息的线程启动 ServerHandler handler = new ServerHandler(); Thread t = new Thread(handler); t.start(); System.out.println("请开始聊天吧!"); String message = null; long time = System.currentTimeMillis()-500; while(true){ message = scanner.nextLine(); if(System.currentTimeMillis()-time>=500){ pw.println(message); time = System.currentTimeMillis(); }else{ System.out.println("您说话过快..."); time = System.currentTimeMillis(); } } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { Client client; try { client = new Client(); client.start(); } catch (Exception e) { e.printStackTrace(); System.out.println("服务端启动失败"); } } /** * 该线程专门用来循环读取服务端发送过来的消息并 * 输出到客户端的控制台上 */ private class ServerHandler implements Runnable{ public void run(){ try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in,"UTF-8"); BufferedReader br = new BufferedReader(isr); String message = null; while((message = br.readLine())!=null){ System.out.println(message); } } catch (Exception e) { } } } } 服务器server //聊天室服务端 public class Server { /** * 运行在服务端的ServerSocket主要负责 * 两个工作: * 1:向系统申请服务端口,客户端就是通过这个 * 端口与服务端应用程序建立连接的。 * 2:监听服务端口,一旦客户端通过该端口尝试 * 连接时,ServerSocket就会实例化一个Socket * 与该客户端通讯。 */ private ServerSocket server; //存放所有客户端的输出流,用于广播消息 private List<PrintWriter> allOut; public Server() throws Exception{ try { /* * 实例化ServerSocket时需要指定 * 服务端口,客户端就是通过这个 * 端口与服务端建立连接的。 * * 该端口不能与系统其它程序申请的 * 端口冲突,否则会抛出异常。 * address already in use */ server = new ServerSocket(8088); allOut = new ArrayList<PrintWriter>(); } catch (Exception e) { throw e; } } public void start(){ try { /* * ServerSocket提供方法: * Socket accept() * 该方法是一个阻塞方法,调用后会 * 一致等待客户端的连接,一旦一个 * 客户端通过ServerSocket申请的端口 * 建立连接,那么accept方法会返回 * 一个Socket实例,通过该Socket实例 * 可以与建立连接的客户端进行通讯。 */ while(true){ System.out.println("等待客户端连接..."); Socket socket = server.accept(); System.out.println("一个客户端连接了!"); //启动一个线程来处理该客户端交互 ClientHandler handler = new ClientHandler(socket); Thread t = new Thread(handler); t.start(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { try { Server server = new Server(); server.start(); } catch (Exception e) { e.printStackTrace(); System.out.println("服务端启动失败"); } } //该线程任务是用于处理与指定客户端的交互工作 private class ClientHandler implements Runnable{ //当前线程通过这个Socket与指定客户端交互 private Socket socket; //该客户端的地址信息 private String host; public ClientHandler(Socket socket){ this.socket = socket; //通过Socket获取远端计算机地址信息 InetAddress address = socket.getInetAddress(); //获取IP地址的字符串形式 host = address.getHostAddress(); } public void run(){ PrintWriter pw = null; try { /* * Socket提供方法: * InputStream getInputStream() * 通过获取的输入流读取的字节就是来自远端 * 发送过来的数据,对于服务端而言,远端 * 指的就是客户端。 */ InputStream in = socket.getInputStream(); InputStreamReader isr= new InputStreamReader(in,"UTF-8"); BufferedReader br= new BufferedReader(isr); //通过Socket获取输出流,用于将消息发送给客户端 OutputStream out = socket.getOutputStream(); OutputStreamWriter osw= new OutputStreamWriter(out,"UTF-8"); pw = new PrintWriter(osw,true); //将该客户端的输出流存入共享集合 synchronized (allOut) { allOut.add(pw); } String message = null; /* * 使用br.readLine读取客户端发送过来的 * 一行字符串时,该方法会处于阻塞状态, * 直到客户端真实发送过来一行,这里才会 * 返回。 * 但是当客户端断开连接时,br.readLine * 会根据客户端不同操作系统有不同的反馈。 * 当windows的客户端断开,br.readLine方法 * 会抛出异常。 * 当linux的客户端断开,br.readLine方法 * 会返回null。 * */ while((message = br.readLine())!=null){ //将消息转发给所有客户端 /* * 线程在遍历集合时的操作要与集合的 * 增删元素互斥! */ synchronized (allOut) { for(PrintWriter o : allOut){ o.println(host+"说:"+message); } } } } catch (Exception e) { } finally{ //处理客户端断开连接后的操作 //将该客户端的输出流从共享集合中移除 synchronized (allOut) { allOut.remove(pw); } try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }

#XML

XML 指可扩展标记语言(EXtensible Markup Language),是独立于软件和硬件的信息传输工具,可以存储简单数据,可扩展就是可以自定义标签。

XML 元素指的是从开始标签直到结束标签的部分(包括标签)。元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。XML对大小写是敏感的,标记< Letter> 和标记 < letter> 是不一样的。XML要求必须有根元素(根元素:不被其它元素包围),根元素只能有一个。CDATA段格式:< ! [ CDATA [ 文本内容 ] ] > 当在xml中某一段内容想作为普通信息看待,而其中又出现了大量的xml敏感字符时,若我们使用实体引用替换显然是件很麻烦的事情,并且还使得xml的易读性变差,这时我们可以使用CDATA段来解决。特殊标签中的实体引用都被忽略,所有内容被当成一整块文本数据对待 ##解析XML文档

需要导入dom4j.jar,maven项目就是方便导入各种jar包

解析XML的大致步骤

创建SAXReader(SAX:Simple API for XML)使用SAXReader读取XML文档并生成Document对象.这一步就是DOM解析耗时耗资源的地方,因为要先将XML文档全部读取并转换为一个Document对象保存到内存。通过Document获取根元素通过根元素按照XML的结构逐级获取子元素以达到遍历XML文档数据的目的 常用方法Element getRootElement():Document获取根元素的方法 Element的每一个实例用于表示XML文档中的一对标签。 Element也提供了获取标签相关信息的方法String getName():获取当前元素的名字Element element(String name):获取当前元素中指定名字的子元素List elements():获取当前元素中所有子元素List elements(String name):获取当前元素中所有同名子元素String getText():获取元素中间的文本,开始标签与结束标签中间的文本信息String elementText(String name):获取当前元素中指定名字子元素中间的文本Attribute attribute(String name):获取当前元素中指定名字的属性Attribute的每一个实例用于表示一个属性 - String getName():获取属性名 - String getValue():获取属性值 例子部分程序 SAXReader reader = new SAXReader(); Document doc = reader.read(new FileInputStream("emplist.xml") ); Element root = doc.getRootElement(); List<Element> list = root.elements(); String gender = empEle.elementText("gender"); Attribute attr = empEle.attribute("id"); int id = Integer.parseInt(attr.getValue());

##生成XML文档 生成XML文档的大致步骤:

创建一个Document对象向Document对象中添加根元素向根元素中逐级追加子元素以形成XML文档结构。创建XmlWriter通过XmlWriter写出Document对象以形成xml文档关闭XmlWriter 常用方法Element addElement(String name):Document添加根元素的方法,添加给定名字的根元素,并将其以Element实例形式返回,以便基于根元素继续追加操作。需要注意,该方法只能调用一次,因为一个文档中只能有一个根元素Element addElement(String name):向当前元素中添加给定名字的子元素Element addText(String text):向当前元素中添加文本,返回值为当前元素,便于对当前元素继续其他操作Element addAttribute(String name,String value):向当前元素中添加指定名字及对应值的属性 部分示例代码 Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("list"); Element empEle = root.addElement("emp"); Element nameEle = empEle.addElement("name"); nameEle.addText(emp.getName()); empEle.addAttribute("id", emp.getId()+""); XMLWriter writer = null; writer = new XMLWriter(new FileOutputStream("myemp.xml"),OutputFormat.createPrettyPrint() writer.write(doc); System.out.println("写出完毕!");

##XPATH

XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。

路径表达式语法:

斜杠(/)作为路径内部的分割符。 同一个节点有绝对路径和相对路径两种写法: 路径(absolute path)必须用"/“起首,后面紧跟根节点,比如/step/step/…。 相对路径(relative path)则是除了绝对路径以外的其他写法,比如 step/step, 也就是不使用”/"起首。

"."表示当前节点。

"…"表示当前节点的父节点

nodename(节点名称):表示选择该节点的所有子节点

“/”:表示选择根节点

“//”:表示选择任意位置的某个节点

“@”: 表示选择某个属性

谓语:

通配符的使用如下:

"*"表示匹配任何元素节点。

"@*"表示匹配任何属性值。

node()表示匹配任何类型的节点。

转载请注明原文地址: https://www.6miu.com/read-44102.html

最新回复(0)