自学Java基础知识第十八天
day18
1. Thread类中的常用方法
1.1 Thread类线程名字相关功能
1) getName() : 获取到当前线程的名称, 如果没有手动给线程设置名字, 线程有默认名称
Thread-数字 : Thread-0 , Thread-1...
2) setName(String name) : 设置线程名称, 修改线程名为参数name
3) 可以通过创建Thread线程对象同时给线程设计名称
代码
package com.ujiuye.thread; public class Demo01_Thread名称方法 { public static void main(String[] args) { Thread t0 = new Thread() { // 大括号表示Thread类的子类实现过程, 子类中可以直接继承使用父类中的方法功能 @Override public void run() { for(int i = 1; i <= 10; i++) { // 1. getName() : 获取到当前线程的名称 System.out.println(getName() + "---" + i); } } };
// 2. setName(String name) : 设置线程名称, 修改线程名为参数name t0.setName("线程1"); t0.start();
// 3. 通过构造给线程设置名字 new Thread("线程2") { // 大括号表示Thread类的子类实现过程, 子类中可以直接继承使用父类中的方法功能 @Override public void run() { for(int i = 1; i <= 100; i++) { System.out.println(getName() + "---" + i); } } }.start();
Runnable run = new Runnable() { @Override public void run() { for(int i = 1; i <= 10; i++) { System.out.println("runnable---" + i); } } };
// 4. 通过构造给线程设置名字 Thread t = new Thread(run,"线程3"); t.start(); System.out.println(t.getName()); } } |
1.2 获取当前线程对象
如果某段代码正在运行--->这代代码所在的方法正在运行--->方法一定在一个线程中正在运行, 于是可以获取到当前正在运行的线程对象
- Thread类中有一个静态方法 currentThread() :
表示获取到当前正在运行的线程对象, 返回值类型Thread, 可以链式调用
代码
package com.ujiuye.thread; public class Demo01_Thread名称方法 { public static void main(String[] args) { Thread t0 = new Thread() { // 大括号表示Thread类的子类实现过程, 子类中可以直接继承使用父类中的方法功能 @Override public void run() { for(int i = 1; i <= 10; i++) { // 1. getName() : 获取到当前线程的名称 System.out.println(getName() + "---" + i); } } };
// 2. setName(String name) : 设置线程名称, 修改线程名为参数name t0.setName("线程1"); t0.start();
Runnable run = new Runnable() { @Override public void run() { for(int i = 1; i <= 10; i++) { // 5. Thread中static currentThread()获取到线程对象 System.out.println(Thread.currentThread().getName() + "----" + i); } } };
// 4. 通过构造给线程设置名字 Thread t = new Thread(run,"线程3"); t.start(); //System.out.println(t.getName()); } } |
1.3 线程休眠
- Thread类中, 有static sleep(long time):
当线程执行到sleep方法, 需要休眠参数time毫秒值时间, 线程一旦休眠, 休眠时间段内失去了竞争CPU的资格
- 方法重写:
发生在子父类继承关系中,或者类与接口实现关系中, 子类或者实现类认为父类中方法功能没有达到子类确切需求, 子类可以重写从父类继承来的方法功能
重写是为了让方法越写越好
1) 子类重写方法功能返回值类型, 方法名, 参数列表上与父类方法一致
2) 子类重写方法权限修饰符范围上大于等于父类
3) 子类重写方法异常小于等于父类中异常
run方法功能从父类Thread中继承来的, run在父类中没有任意异常, 子类重写也不能声明异常, 有异常发生, 要求处理掉, try...catch
- 实际开发中使用sleep:
系统中每天12点, 在指定路径下载核心给出的当天对账文件, 需要去读取核心给出的文件内容, 一共给3次机会反复验证文件是否存在
for(int i = 1; i <= 3; i++){
if( 判断文件不存在 || 文件.length() == 0){
Thread.sleep(1000);
}else{
使用IO流资源读取文件内容;
break;
}
}
代码
package com.ujiuye.thread; public class Demo02_Thread线程休眠 { public static void main(String[] args) { new Thread("小强") { @Override public void run(){ for(int i = 1; i <= 10; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "----" + i); } } }.start();
new Thread("小弱") { @Override public void run(){ for(int i = 1; i <= 10; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "----" + i); } } }.start(); } } |
1.4 守护线程
- 守护线程的对比理解:
象棋中:
守护线程 : 兵, 车, 马, 炮..., 这些存在都是为了守护 将/帅
非守护线程 : 将/帅 , 不需要守护别人, 保护好自己就可以
- Thread类中有方法功能 setDaemon(boolean boo)
将一个线程设置为守护线程(用户线程, 后台线程), 参数boo设置为true,守护线程存在一般都是为了给程序或者应用提供稳定的运行环境
- 守护线程执行机制:
如果代码中的所有非守护线程都执行完毕, 那么守护线程也直接跟着结束
代码
package com.ujiuye.thread; public class Demo03_守护线程 { public static void main(String[] args) { Thread t1 = new Thread("守护线程") { @Override public void run(){ for(int i = 1; true; i++) { System.out.println(getName() + "----" + i); } } };
t1.setDaemon(true); t1.start();
new Thread("非守护线程") { @Override public void run(){ for(int i = 1; i <= 10; i++) { System.out.println(getName() + "----" + i); } } }.start(); } } |
1.5 线程优先级
- 通过方法功能,在参数列表中给出线程对应的优先级, 优先级就是整数, 整数数值越大, 优先级别就越高
- 在Thread类中, 提供静态常量成员, 用于表示线程的优先级
最高优先级--->10
普通优先级--->5
最低优先级--->1
- 如果某线程优先级别比较高, 那么只是在前期增加线程竞争CPU的成功率, 不论怎样, 多线程的并发执行, 都具有很大的随机性
代码
package com.ujiuye.thread; public class Demo04_线程优先级 { public static void main(String[] args) { Thread t0 = new Thread("低优先级") { @Override public void run() { for(int i = 1; i <= 10; i++) { // 1. getName() : 获取到当前线程的名称 System.out.println(getName() + "---" + i); } } }; // 1.通过方法功能,在参数列表中给出线程对应的优先级, 优先级就是整数, 整数数值越大, 优先级别就越高 // t0设置低优先级 t0.setPriority(Thread.MIN_PRIORITY); t0.start();
Thread t1 = new Thread("中优先级") { // 大括号表示Thread类的子类实现过程, 子类中可以直接继承使用父类中的方法功能 @Override public void run() { for(int i = 1; i <= 10; i++) { // 1. getName() : 获取到当前线程的名称 System.out.println(getName() + "---" + i); } } }; // t1设置正常优先级 t1.setPriority(Thread.NORM_PRIORITY); t1.start();
Thread t2 = new Thread("高优先级") { // 大括号表示Thread类的子类实现过程, 子类中可以直接继承使用父类中的方法功能 @Override public void run() { for(int i = 1; i <= 10; i++) { // 1. getName() : 获取到当前线程的名称 System.out.println(getName() + "---" + i); } } };
// t2设置高优先级 t2.setPriority(Thread.MAX_PRIORITY); t2.start(); } } |
2. 线程安全问题
2.1多线程发生线程安全问题
案例 : 某电影院要放映电影<葫芦娃救爷爷>, 本场电影共有影票100张, 客户可以从美团, 猫眼, 以及影院三个渠道购买影票, 每个渠道售票独立, 三个渠道共同销售100张票, 直到售完为止, 请将三个渠道售票过程以多线程模拟出来
分析 : 将美团, 猫眼, 影院设计成3个线程, 每一个线程都可以独立进行售票
总结多线程数据安全问题发生原因:
某线程正在运行, 线程没有运行完毕, 资源就被其他线程抢夺走了, 导致当前代码中的数据发生混乱, 甚至是错误, 因此发生线程安全问题
解决多线程数据安全问题 : 线程运行时, 保证当前线程运行完毕, 运行完毕之后其他线程再争夺资源, 保证代码的完整性
有安全问题的代码
package com.ujiuye.thread;
// SaleTickets类表示销售影票线程类 public class SaleTickets implements Runnable { // tickets表示目前剩余的票数 int tickets = 100; Object obj = new Object(); @Override public void run() {// 影票销售过程 while(tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在销售第"+ tickets-- + "张票"); } } } |
package com.ujiuye.thread; public class TestSaleTickets { public static void main(String[] args) { 同步代码块解决线程安全问题 SaleTickets st = new SaleTickets(); // 定义出三个线程, 分别对应美团, 猫眼, 影院 Thread t0 = new Thread(st,"美团"); Thread t1 = new Thread(st,"猫眼"); Thread t2 = new Thread(st,"影院");
t0.start(); t1.start(); t2.start(); } } |
2.2 同步代码块解决线程安全问题
1. 同步代码块 : 语法结构
synchronized(锁对象){
需要保证完整性代码;
}
- 说明:
1) synchronized : 关键字, 表示同步的概念
2) 小括号需要锁对象:对象数据类型方面可以是任意一种引用数据类型, 锁对象必须唯一
3) 哪些代码逻辑需要保证完成, 中途不被打断, 将这些代码设计到同步代码块的大括号中
- 同步代码块解决线程安全问题的原理
1. 进同步代码块之前, 验证锁对象存在, 获取锁对象
2. 出同步代码块, 归还锁对象
3. 没有锁对象只能在同步代码块之外等待, 无法进入到同步代码块执行
代码
package com.ujiuye.thread;
// SaleTickets类表示销售影票线程类 public class SaleTickets implements Runnable { // tickets表示目前剩余的票数 int tickets = 100; Object obj = new Object(); @Override public void run() {// 影票销售过程 while(tickets > 0) { // 同步代码块解决多线程安全问题 synchronized (obj) { if(tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在销售第"+ tickets-- + "张票"); } } } } } |
package com.ujiuye.thread; public class TestSaleTickets { public static void main(String[] args) { 同步代码块解决线程安全问题 SaleTickets st = new SaleTickets(); // 定义出三个线程, 分别对应美团, 猫眼, 影院 Thread t0 = new Thread(st,"美团"); Thread t1 = new Thread(st,"猫眼"); Thread t2 = new Thread(st,"影院");
t0.start(); t1.start(); t2.start(); } } |
2.3 同步方法解决线程安全问题
1. 语法结构:
修饰符 synchronized 返回值类型 方法名(参数列表){
需要保证完整执行代码逻辑;
}
- 同步方法锁对象
a : 方法非静态方法, 同步锁就是this关键字
b : 方法静态方法, 同步锁就是当前类型对应.class字节码文件对象
代码
package com.ujiuye.thread;
// SaleTickets类表示销售影票线程类 public class SaleTickets2 implements Runnable { // tickets表示目前剩余的票数 static int tickets = 40; @Override public void run() {// 影票销售过程 while(tickets > 0) { // sale(); sale2(); } }
// 定义出一个同步方法 public synchronized void sale() { //synchronized (this) { if(tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在销售第"+ tickets-- + "张票"); } //}
}
public static synchronized void sale2() { synchronized (SaleTickets2.class) { if(tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在销售第"+ tickets-- + "张票"); } } } } |
package com.ujiuye.thread; public class TestSaleTickets { public static void main(String[] args) { SaleTickets2 st = new SaleTickets2(); // 定义出三个线程, 分别对应美团, 猫眼, 影院 Thread t0 = new Thread(st,"美团"); Thread t1 = new Thread(st,"猫眼"); Thread t2 = new Thread(st,"影院");
t0.start(); t1.start(); t2.start(); } } |
3.线程生命周期
从一个线程创建, 运行线程, 到线程结束, 最多经历5种状态, 多种状态贯穿在一起形成线程生命周期
- 新建状态 : 创建出一个线程类对象
- 就绪状态 : 通过start方法开启一个线程, 具有代码独立运行通道, 需要竞争到CPU资源
- 运行状态 : 需要独立运行代码run方法中, 正在线程中运行
- 阻塞状态 : sleep, 等待锁对象, wait长久等待, IO流阻塞(举例 : Scanner键盘录入)
- 死亡状态 : run方法运行完毕, 或者发生异常
JDK, Thread类中, 有内部枚举类型, State, 用于表示一个线程可以出现状态
- 枚举类型与class 类存在非常相似, 枚举类型创建的对象个数固定的
4.线程池
1. 没有线程池:
如果程序中,有很多独立线程任务, 每一个线程任务执行过程比较简单, 每一次任务执行, 需要开启线程, 需要开辟独立代码运行通道(占有内存) , 当任务运行完毕, 独立线程通道变回变成内存垃圾, 等待回收; 代码执行时间还是内存空间使用, 没有优势
- 有线程池:
程序中预先准备出一个具有指定线程个数线程池, 当代码中有很多线程任务, 任务可以使用线程池中的线程运行, 线程任务运行完毕, 线程回到线程池中, 继续为下一个任务进行服务, 如此可以避免频繁开辟线程通道, 回收线程通道过程, 代码在执行时间和内存空间利用上,比较有优势
- 线程池实现步骤:
1) Executors : 创建出线程池的工厂类, 来自于 java.util.concurrent包
2) 在Executors 中, 有静态方法:
static newFixedThreadPool(int n) : 用于创建出一个具有指定线程数量n的线程池
3) newFixedThreadPool(int n) 方法返回值类型ExecutorService接口, ExecutorService接口可以操作线程池, 完成线程任务, 而方法实际返回值是ExecutorService接口的实现类对象
4) ExecutorService接口中, 有方法功能操作线程池中的线程:
submit(Runnable run) : 将参数run所表示的线程任务, 提交到线程中, 获取线程池中线程执行任务; 任务执行完毕, 线程重新回归到线程池中
5) 当使用线程池完成任务之后, 需要关闭线程池, 否则线程池就会一直等待给新的任务执行, 使用ExecutorService接口中方法,关闭线程池
a : shutdown() : 表示销毁线程池, 如果有已经提交的任务, 保证完成, 完成之后再销毁线程池
b : shutdownNow() : 表示销毁线程池, 正在执行的线程任务, 保证完成, 但是已经通过submit提交的,没有运行的任务,不执行了,直接结束
代码
package com.ujiuye.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo05_线程池 { public static void main(String[] args) { Runnable run = new Runnable() { @Override public void run() { for(int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "--" + i); } } };
Runnable run1 = new Runnable() { @Override public void run() { for(int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "--" + i); } } };
Runnable run2 = new Runnable() { @Override public void run() { for(int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "--" + i); } } };
Runnable run3 = new Runnable() { @Override public void run() { for(int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "--" + i); } } };
// 1.创建出一个线程池,有2个固定线程 ExecutorService es = Executors.newFixedThreadPool(2); // 2. 使用ExecutorService中submit方法提交准备好的4个线程任务 es.submit(run); es.submit(run1); es.submit(run2); es.submit(run3);
// es.shutdown(); es.shutdownNow(); } } |
5. JDK8接口新特性
- JDK8之前, 接口中只能定义出抽象方法, 在JDK8版本, 支持在接口中定义出非抽象方法, 可以定义出默认方法(default) , 可以定义静态方法(static)
- 接口中抽象方法, 默认修饰public abstract
- 接口只能定义成员常量 , 默认修饰 public static final
5.1 接口中的默认方法
在JDK8版本之前, 接口中全部都是抽象方法, 接口实现类需要将接口中所有抽象方法重写, 实现类才能正常创建对象使用; 可是, 随着需求推进, 有可能涉及到修改接口中方法, 向接口中添加方法
a : 如果只能向接口中添加抽象方法, 当前接口的所有实现类都会受到影响, 都需要重写方法或者变成抽象类, 影响所有实现类, 灵活度不够
b : 于是在JDK8版本, 可以向接口中添加默认方法, 默认方法可以有方法体, 不需要实现类必须重写, 实现类有选择余地, 可以重写, 可以不重写直接继承到父接口中的默认方法
默认方法接口中使用:
- 接口中默认方法, 使用default关键字进行修饰, 可以有方法体, 默认方法可以被实现类直接继承使用, 可以不重写
- 实现类可以重写从父接口中继承来的default默认方法, 类中default默认修饰不能写, 写出来就报错, 实现类中重写的默认方法,需要使用public进行修饰
- 一个类可以同时实现多个接口, 但是多个父接口中有一样的默认方法定义, 就会冲突报错, 要求实现类必须将冲突默认方法进行重写, 如果实现类中重写的默认方法实现过程需要某一个父接口实现过程相符, 可以调用 : 父接口.super.方法名();
- 一个类继承一个父类同时实现多个接口, 父优先, 不管重写, 继承, 一律优先以父类为标准, 父类没有,再到接口中进行重写或者继承
代码
package com.ujiuiye.interfacedemo; public interface MyInter { // 1. 定义出抽象方法 public abstract void fun();
// 2. 定义出默认方法 default void fun1() { System.out.println("MyInter接口中的默认方法fun1"); }
default void fun2() { System.out.println("接口中的默认方法fun2"); } } |
package com.ujiuiye.interfacedemo; public interface MyInterOther { default void fun1() { System.out.println("MyInterOther接口中的默认方法fun1"); } } |
package com.ujiuiye.interfacedemo; public class MyInterImpl implements MyInter,MyInterOther{
// 1. 重写父接口MyInter中的抽象方法 @Override public void fun() { System.out.println("实现类重写父接口中抽象方法fun"); }
// 2. 重写父接口MyInter中的默认方法, 修饰符需要使用public @Override public void fun2() { System.out.println("实现类重写的父接口中的默认方法"); }
// 3. 重写两个父接口中的冲突默认方法, 不重写会报错 @Override public void fun1() { // 需要使用父接口MyInterOther中默认方法 MyInterOther.super.fun1(); } } |
package com.ujiuiye.interfacedemo; public class TestInterface { public static void main(String[] args) { MyInterImpl my = new MyInterImpl(); my.fun(); my.fun1(); my.fun2(); } } |