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

#多线程基础
##概念

  • 进程(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的大致步骤

  1. 创建SAXReader(SAX:Simple API for XML)
  2. 使用SAXReader读取XML文档并生成Document对象.这一步就是DOM解析耗时耗资源的地方,因为要先将XML文档全部读取并转换为一个Document对象保存到内存。
  3. 通过Document获取根元素
  4. 通过根元素按照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文档的大致步骤:

  1. 创建一个Document对象
  2. 向Document对象中添加根元素
  3. 向根元素中逐级追加子元素以形成XML文档结构。
  4. 创建XmlWriter
  5. 通过XmlWriter写出Document对象以形成xml文档
  6. 关闭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()表示匹配任何类型的节点。
    这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值