#多线程基础
##概念
- 进程(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()表示匹配任何类型的节点。