本章内容:
1.正则表达式简介
2.多线程
3.maven编程
1.正则表达式简介
匹配规则
正则表达式是一套标准,它可以用于任何语言。Java标准库的java.util.regex包内置了正则表达式引擎,在Java程序中使用正则表达式非常简单
注意Java字符串用\\表示\,比如正常的202\d\d
,在Java中的202\\d\\d
正则表达式也有特殊字符,比如转义字符\
,对于正则表达式a\&c
来说,对应的Java字符串是a\\&c
,因为\
也是Java字符串的转义字符,两个\\
实际上表示的是一个\
:
透过现象看本质,Java与python正则表达式关于正则表达式的使用方法是相同的,可以参考使用,注意Java字符串用\\表示\
正则表达式 | 规则 | 可以匹配 |
---|---|---|
A | 指定字符 | A |
\u548c | 指定Unicode字符 | 和 |
. | 任意字符 | a,b,&,0 |
\d | 数字0~9 | 0~9 |
\w | 大小写字母,数字和下划线 | a~z,A~Z,0~9,_ |
\s | 空格、Tab键 | 空格,Tab |
\D | 非数字 | a,A,&,_,…… |
\W | 非\w | &,@,中,…… |
\S | 非\s | a,A,&,_,…… |
A* | 任意个数字符 | 空,A,AA,AAA,…… |
A+ | 至少1个字符 | A,AA,AAA,…… |
A? | 0个或1个字符 | 空,A |
A{3} | 指定个数字符 | AAA |
A{2,3} | 指定范围个数字符 | AA,AAA |
A{2,} | 至少n个字符 | AA,AAA,AAAA,…… |
A{0,3} | 最多n个字符 | 空,A,AA,AAA |
^ | 开头 | 字符串开头 |
$ | 结尾 | 字符串结束 |
[ABC] | […]内任意字符 | A,B,C |
[A-F0-9xy] | 指定范围的字符 | A,……,F,0,……,9,x,y |
[^A-F] | 指定范围外的任意字符 | 非A~F |
String.matches(regex)方法:只能匹配目标字符串是否满足给定的正则表达式规则,返回值是Boolean类型
1 | public class Main { |
分组匹配
我们前面讲到的(…)可以用来把一个子规则括起来,这样写learn\s(java|php|go)就可以更方便地匹配长字符串了。
实际上(…)还有一个重要作用,就是分组匹配。
前面已经了解到String.matches(regex)来判断是否满足目标正则表达式。但是如何提取匹配的子串?这就必须引入java.util.regex包,用Pattern对象匹配,匹配后获得一个Matcher对象,如果匹配成功,就可以直接从Matcher.group(index)返回子串:
1 | public class Main { |
运行上述代码,会得到两个匹配上的子串010和12345678。
要特别注意,Matcher.group(index)方法的参数用1表示第一个子串,2表示第二个子串。如果我们传入0会得到什么呢?答案是010-12345678,即整个正则匹配到的字符串。
搜索和替换
1 | public class Main { |
我们获取到Matcher对象后,不需要调用matches()方法(因为匹配整个串肯定返回false),而是反复调用find()方法
替换字符串
使用正则表达式替换字符串可以直接调用String.replaceAll()
,它的第一个参数是正则表达式,第二个参数是待替换的字符串。举例说明:
语言格式:strings.replaceAll(“正则表达式规则”,a) 用a替换strings里面的正则表达式规则
1 | public class Main{ |
2.多线程
进程 vs 线程
进程和线程是包含关系,但是多任务既可以由多进程实现,也可以由单进程内的多线程实现,还可以混合多进程+多线程。
和多线程相比,多进程的缺点在于:
- 创建进程比创建线程开销大,尤其是在Windows系统上;
- 进程间通信比线程间通信要慢,因为线程间通信就是读写同一个变量,速度很快。
而多进程的优点在于:
多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。
多线程
Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部,我们又可以启动多个线程。此外,JVM还有负责垃圾回收的其他工作线程等。
因此,对于大多数Java程序来说,我们说多任务,实际上是说如何使用多线程实现多任务。
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。例如,播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步。因此,多线程编程的复杂度高,调试更困难。
Java多线程编程的特点又在于:
- 多线程模型是Java程序最基本的并发模型;
- 后续读写网络、数据库、Web开发等都依赖Java多线程模型。
创建新线程
Java语言内置了多线程支持。当Java程序启动的时候,实际上是启动了一个JVM进程,然后,JVM启动主线程来执行main()方法。在main()方法中,我们又可以启动其他线程。
要创建一个新线程非常容易,我们需要实例化一个Thread实例,然后调用它的start()方法
方法一:从Thread派生一个自定义类,然后覆写
创建多线程程序的第一种方式:创建Thread类的子类
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要执行什么任务?)
3.创建Thread类的子类对象
4.调用Thread类中的方法start方法,开启新的线程,执行run方法
拓展:关于start()的说明:
void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行
1 | public class Main { |
方法二:创建Thread实例时,传入一个Runnable实例
创建多线程程序的第二种方式:实现Runnable接口
java.lang.Runnable:Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
java.lang.Thread类的构造方法:
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
1 | public class Main { |
方法三:用匿名内部类语法进一步简写:
匿名内部类方式实现线程的创建
匿名:没有名字
内部类:写在其他类内部的类
格式:
1 | new 父类/接口(){ |
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
1 | public class Demo01InnerClassThread { |
线程的三种创建方式综合演练:
1 | package com.Alibaba; |
小结:
Java用Thread对象表示一个线程,通过调用start()启动一个新线程;
一个线程对象只能调用一次start()方法;
线程的执行代码写在run()方法中;
线程调度由操作系统决定,程序本身无法决定调度顺序;
Thread.sleep()可以把当前线程暂停一段时间,单位是毫秒(Thread.sleep在编译时会出现中断异常InterruptedException,需要使用try catch捕获处理异常)
线程安全
当有多个线程抢夺共享数据时就会出现争抢资源的情况(与python的多进程同理),这时就会出现线程安全问题(比如卖票问题就会出现卖出了不存在的票和重复的票的线程安全问题)
- 解决线程安全问题的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码
1 | public class ThreadDemo { |
- 解决线程安全问题的一种方案:使用Lock锁
包路径:java.util.concurrent.locks.Lock(Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。)
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
使用步骤:
- 在成员位置创建一个ReentrantLock对象(可以采用多态方法:左父右子Lock lock = new ReentrantLock();这是因为java.util.concurrent.locks.ReentrantLock implements Lock接口,实现类继承父类)
- 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
- 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
举例说明:
1 | import java.util.concurrent.locks.Lock; |
线程状态
- New:新创建的线程,尚未执行;
- Runnable:运行中的线程,正在执行run()方法的Java代码;
- Blocked:运行中的线程,因为某些操作被阻塞而挂起;
- Waiting:运行中的线程,因为某些操作在等待中;
- Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
- Terminated:线程已终止,因为run()方法执行完毕。
join()方法:优先运行子线程,主线程卡在原地,子线程结束后,运行主线程后面的代码,即join就是指等待该线程结束,然后才继续往下执行自身线程。(join方法会抛出编译期异常,必须要try catch或throws来处理异常)
Thread.sleep()方法:线程暂停,时间为毫秒级
守护进程
Java程序入口就是由JVM启动main线程,main线程又可以启动其他线程。当所有线程都运行结束时,JVM退出,进程结束。
如果有一个线程没有退出,JVM进程就不会退出。所以,必须保证所有线程都能及时结束
1 | // 创建方式:只是在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程 |
线程池
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
线程池的使用步骤:
Java里面线程池的顶级接口是java.util.concurrent.Executor
(JDK1.5之后提供的),但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池,声明线程数量:
1 | Executors类中的静态方法: |
java.util.concurrent.ExecutorService:线程池接口
- 用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个 Runnable 任务用于执行
- 关闭/销毁线程池的方法:通常不推荐这么做,一旦销毁了进程池,后续就不能再次获取进程了
void shutdown()
线程池的使用步骤:
- 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
- 调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
举例说明:
1 | import java.util.concurrent.ExecutorService; |
PS:java的线程,进程与python的线程,进程类似。
- 对于Python:进程对象的join方法使得主进程在等待子进程运行完毕之后,再运行下一行代码( 而不是主进程先运行代码,等待子进程运行结束 );而python的线程之间没有先后之分,主线程需要等待子线程全部运行结束后,才能释放掉子线程所占用的资源。主线程代表了一个进程的生命周期,而一个进程一定要等到内部包含的所有线程都运行结束后,才能释放资源
- python的 join 会卡住主线程,并让当前已经 start 的子线程继续运行,直到调用.join的这个线程运行完毕
- 对于Java:线程的join方法优先运行子线程,主线程卡在原地,子线程结束后,运行主线程后面的代码,即join就是指等待该线程结束,然后才继续往下执行自身线程
- Java的线程池是不会自动关闭的,若没有shutdown()方法,则进程池文件会一直运行下去
3.Maven基础
Maven就是是专门为Java项目打造的管理和构建工具,它的主要功能有:
- 提供了一套标准化的项目结构;
- 提供了一套标准化的构建流程(编译,测试,打包,发布……);
- 提供了一套依赖管理机制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16a-maven-project
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ └── resources
│ └── test
│ ├── java
│ └── resources
└── target
Ps:
1.main目录用来存放Java源代码,而test目录用来存放Java测试代码
2.resources用来存放配置文件,资源文件(图片,js,css等等)
3.pom.xml项目描述文件:groupId类似于Java的包名,artifact类似于Java的类名,version代表版本号
4.target 存放所有编译、打包生成的文件
4.lambda表达式
Lambda表达式的标准格式:
1 | 由三部分组成: |
案例Ⅰ:lambda表达式,实现多线程
1 | public class ThreadPool { |
案例Ⅱ:
1 | public class ThreadPool { |