Java
一、多线程
1.1 并发与并行
- 并发
指两个或多个事件在同一个时间段内发生。
- 并行
指两个或多个事件在同一时刻发生(同时发生)。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。
- 注意事项
单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
1.2 线程与进程
- 进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
1.3 线程调度
- 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
- 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
1.4 多线程创建
Java使用
java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。
1.4.1 继承Thread类
继承Thread类创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
public class MyThread extends Thread{
//重写run方法
@Override
public void run() {
for (int i = 1; i <=100; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
MyThread m1=new MyThread();
MyThread m2=new MyThread();
MyThread m3=new MyThread();
MyThread m4=new MyThread();
m1.start();
m2.start();
m3.start();
m4.start();
System.out.println("main方法执行");
}
}
1.4.2 实现Runnable接口
Runnable接口可以看出线程执行接口,可以交由线程执行的类
public class MyRunnableTest {
public static void main(String[] args) {
//创建线程可执行类
MyRunnable m=new MyRunnable();
//创建线程对象 将可执行类交由线程对象执行
Thread t1=new Thread(m);
Thread t2=new Thread(m);
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <=100; i++) {
System.out.println(i);
}
}
}
1.4.3 Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
1.适合多个相同的程序代码的线程去共享同一个资源。
2.可以避免java中的单继承的局限性。
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4.线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
本质线程进行执行就是实现了Runable接口(Thread类实现Runable接口),继承Thread类重写run方法本质其实就是填写实现runnable接口的run方法
实现runnable接口的方式才是正常使用线程的方式,将线程需要执行的代码通过实现runnable接口的形式书写,创建线程时传入相应的实现类对象,交由线程运行
面试题
线程的创建方式有几种?
1、继承Thread类重写run方法
2、实现runnable接口重写run方法
3、使用callable与futrue接口创建线程
4、线程池创建
1.5 Thread类
1.5.1 构造方法
方法名 | 说明 |
---|---|
public Thread() | 分配一个新的线程对象 |
public Thread(String name) | 分配一个指定名字的新的线程对象 |
public Thread(Runnable target) | 分配一个带有指定目标新的线程对象 |
public Thread(Runnable target,String name) | 分配一个带有指定目标新的线程对象并指定名字 |
1.5.2 线程常用方法
方法名 | 说明 |
---|---|
public String getName() | 获取当前线程名称。 |
public void setName(String name) | 设置线程的名称 |
public void start() | Java虚拟机调用此线程的run方法 |
public void run() | 此线程要执行的任务在此处定义代码 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
- 代码演示
Thread t1=new Thread(){
@Override
public void run() {
System.out.println(this.getName());
//获取线程名称
//默认使用Thread-index命名
}
};//创建一个线程
Thread t2=new Thread("t2线程"){
@Override
public void run() {
System.out.println(this.getName());
}
};//创建一个线程
t1.setName("t1线程");
Runnable r=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
Thread t3=new Thread(r,"t3线程");
t1.start();
t2.start();
t3.start();
Thread mainThread = Thread.currentThread();
System.out.println(mainThread.getName());
- 练习
使用线程模拟多个窗口售票,多个窗口买相同票
public class TicketTest {
public static void main(String[] args) {
Runnable r=new Runnable() {
private int ticket=100;//票数
@Override
public void run() {
while(ticket>0){
String name = Thread.currentThread().getName();
System.out.println(name+"买出了一张票,还剩"+ ticket--);
}
System.out.println("票已售完");
}
};
//创建窗口线程
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r,"窗口2");
Thread t3=new Thread(r,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
1.5.3 线程生命周期
- 新建态
也叫初始态,当使用Thread类构造方法创建对象时进入的状态- 可运行态
也叫做就绪态,当线程对象调用start方法时进入的状态- 运行态
不是我们执行的,是电脑分配资源给就绪的线程进行运行,并自动调用对应线程的run方法进入的状态- 阻塞态
在线程执行过程中,由于方法的调用或者代码的书写以及资源的阻塞导致的方法的停滞运行- 死亡态
线程执行结束或者调用方法停止线程并进行资源回收- 一个线程的生命周期
调用构造方法创建线程对象,进入初始态,之后调用start方法进入就绪态,等待计算机分配资源自动调用run方法进入运行态,调用阻塞方法或遇到阻塞代码进入阻塞态,当阻塞结束后或调用唤醒代码进入就绪态,等待分配资源执行,执行结束进入死亡态,进行资源回收释放,在运行过程中也可以调用线程礼让方法,将线程由运行态转换为就绪态。
1.5.4 线程状态方法
方法名 | 说明 |
---|---|
public static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
public final void join() | 等待该线程终止 |
public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
public final void setPriority(int newPriority) | 更改线程的优先级。默认为5, 最小级别:0 ,最大级别:10 |
public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
1.5.4.1 sleep方法
使线程停止运行一段时间,将处于阻塞状态
如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while(true){
Date d=new Date();
System.out.println(sdf.format(d));
//线程休眠 静态方法 使当前线程等待
Thread.sleep(1000);//线程休眠时间 单位毫秒
}
}
1.5.4.2 jion方法
阻塞指定线程等到另一个线程完成以后再继续执行。
在当前线程加入指定线程,只有当前加入的线程执行结束后才能继续向下执行(相当于直接调用)
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Runnable r=new Runnable() {
@Override
public void run() {
for (int i = 1; i <=10; i++) {
//输出线程名以及遍历数字
System.out.println(Thread.currentThread().getName()+"=>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1=new Thread(r,"张三");
Thread t2=new Thread(r,"李四");
Thread t3=new Thread(r,"王五");
t1.start();
t1.join();
t2.start();
t3.start();
}
}
1.5.4.3 setDaemon方法
可以将指定的线程设置成后台线程,守护线程;
创建用户线程的线程结束时,后台线程也随之消亡;
只能在线程启动之前把它设为后台线程
public static void main(String[] args) throws InterruptedException {
Runnable r=new Runnable() {
@Override
public void run() {
for (int i = 1; i <=10; i++) {
//输出线程名以及遍历数字
System.out.println(Thread.currentThread().getName()+"=>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1=new Thread(r,"守护线程1");
Thread t2=new Thread(r,"守护线程2");
//设置为守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
Thread.sleep(2000);
System.out.println("主线程结束");
}
1.5.4.4 setPriority方法
范围从1到10,默认为5 ,设置线程执行的优先级
public static void main(String[] args) {
Runnable r=new Runnable() {
@Override
public void run() {
for (int i = 1; i <=10; i++) {
//输出线程名以及遍历数字
System.out.println(Thread.currentThread().getName()+"=>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1=new Thread(r,"t1");
Thread t2=new Thread(r,"t2");
Thread t3=new Thread(r,"t3");
//设置优先级
t1.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MIN_PRIORITY);
//设置优先级后进行执行时会优先执行优先级高的
//在资源不充足的情况下优先执行
//如果资源充足 可能同时执行 但是也会偏重优先级高的
t1.start();
t2.start();
t3.start();
}
1.5.4.5 yield方法
让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态;
调用了yield方法之后,如果没有其他等待执行的线程,此时当前线程就会马上恢复执行!
线程礼让,会让出当前线程运行的资源,将线程由执行态转换为就绪态,如果资源充足会直接继续执行
public static void main(String[] args) {
Runnable r1=new Runnable() {
@Override
public void run() {
for (int i = 1; i <=10; i++) {
Thread currentThread = Thread.currentThread();
//输出线程名以及遍历数字
System.out.println(currentThread.getName()+"=>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable r2=new Runnable() {
@Override
public void run() {
for (int i = 1; i <=10; i++) {
Thread currentThread = Thread.currentThread();
currentThread.yield();//线程礼让
//输出线程名以及遍历数字
System.out.println(currentThread.getName()+"=>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t1=new Thread(r1,"t1");
Thread t2=new Thread(r2,"t2");
//由于t2每次循环时都执行礼让方法
//所以基本上都是t1线程线执行输出
t1.start();
t2.start();
}
1.6 案例
放水问题,一个水池1000升 放水每秒100升 排水每秒50升
public class Test {
public static void main(String[] args) throws IOException {
//放水问题,一个水池1000升 放水每秒100升 排水每秒50升
MyWaterThread t1=new MyWaterThread(100);
MyWaterThread t2=new MyWaterThread(-50);
t1.setName("放水阀放水");
t2.setName("排水阀排水");
t1.start();
t2.start();
}
}
class MyWaterThread extends Thread{
private static int pool=0;//水池水量
private int add=0;//放水量
public MyWaterThread(int add) {
super();
this.add = add;
}
@Override
public void run() {
Date d1=new Date();
while(pool<1000){
pool+=add;
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+",水池水量:"+pool);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Date d2=new Date();
System.out.println(d2.getTime()-d1.getTime());
}
}
面试题
start
方法与run
方法的区别?
- 书写的不同
run
方法是线程执行时执行的具体方法
start
方法是线程启动执行时的方法无需书写- 线程生命周期转换不同
start
方法是将线程由初始态转换为就绪态
run
方法是将线程由就绪态转换为运行态- 调用方不同
start
方法由我们调用启动线程执行
run
由计算机为线程分配运行资源之后自动调用- 使用不同
start
方法直接调用使用启动线程
run
方法直接调用不会启动线程而是直接运行
二、Properties集合
2.1 Properties集合的概念
java.util.Properties
包下,是继承与HashTable的集合类型数据存储的工具类。底层使用双列进行数据存储,通常是用于读取以.properties结尾的配置文件
- 是一个Map体系的集合类
- Properties可以保存到流中或从流中加载
- 属性列表中的每个键及其对应的值都是一个字符串
2.2 properties配置文件的书写
- properties配置文件默认编码为IOS-8859-1,所以在使用时尽量不要书写中文
- 以#作为单行注释
- 以Key=Value的形式书写
- 所有内容以字符串的形式进行书写
2.3 properties配置文件的获取
2.3.1 properties类的常用方法
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用 Hashtable方法 put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
Set<String> stringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
2.3.2 使用输入流与Properties读取配置文件
//方式1
//使用文件字节/字符输入流
public static void m1() throws Exception{
//创建Propertis类
Properties p=new Properties();
//创建指定文件的读取流
FileInputStream fis=new FileInputStream("E://code/JavaLogic/src/a.properties");
//加载指定的配置文件的输入流
p.load(fis);
//Properties对象在调用load方法加载对应流后会将所有数据进行存储
String property = p.getProperty("lisi");
System.out.println(property);
}
2.3.3 使用类加载器与Properties读取配置文件
// 方式2
// 使用classLoad加载配置文件返回输入流
public static void m2() throws IOException {
// 创建Propertis类
Properties p = new Properties();
//使用当前的类获取class属性调用方法获取类加载器
ClassLoader classLoader = PropertiesTest.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("com/yunhe/day0701/a.properties");
// 加载指定的配置文件的输入流
p.load(resourceAsStream);
// Properties对象在调用load方法加载对应流后会将所有数据进行存储
String property = p.getProperty("zhangsan");
System.out.println(property);
}
2.3.4 ResourceBundle读取配置文件
通过 ResourceBundle.getBundle() 静态方法来获取(ResourceBundle是一个抽象类),这种方式来获取properties属性文件不需要加.properties后缀名,只需要文件名即可
//方式3
//使用ResourceBundle读取配置文件
public static void m3() throws Exception{
ResourceBundle rb=ResourceBundle.getBundle("com/yunhe/day0701/a");
//ResourceBundle加载配置文件默认加载src下 无需后缀
String string = rb.getString("lisi");
System.out.println(string);
}
2.3.5 PropertyResourceBundle读取配置文件
PropertyResourceBundle是ResourceBundle的子类,可以通过输入流的形式进行读取
//方式4
//使用ResourceBundle子类读取配置文件
public static void m4() throws Exception{
//ResourceBundle与Properties一样是通过输入流的形式读取
//(1)使用文件输入流获取任意位置的配置文件
FileInputStream fis = new FileInputStream("E://code/JavaLogic/src/a.properties");
//(2)使用classload获取类路径(src)下的文件
ClassLoader classLoader = PropertiesTest.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("com/yunhe/day0701/a.properties");
PropertyResourceBundle prb=new PropertyResourceBundle(resourceAsStream);
String string = prb.getString("lisi");
System.out.println(string);
}
2.4 注意事项
在使用中遇到的最大的问题可能是配置文件的路径问题,如果配置文件入在当前类所在的包下,那么需要使用包名限定,如:config.properties入在com.test.config包下,则要使用com/test/config/config.properties(通过Properties来获取)或com/test/config/config(通过ResourceBundle来获取);属性文件在src根目录下,则直接使用config.properties或config即可。
每日一点点进步
不进则退