Java多线程-Lesson02
一.Lamda表达式
λ是希腊字母表的第十一位字母,英文名是Lambda
Lamda表达式的出现主要是为了避免匿名内部类过多的原因,它属于函数式编程的范畴
为什么要使用Lamda表达式?
- 避免匿名内部类定义过多
- 可以让你的代码看起来很简洁
- 去掉了没有意义的代码,只留下一堆核心逻辑
主要使用在创建的类只使用了一次时,创建匿名方法太多会很麻烦,Lamda表达式以其简洁的定义方式使得在类的一次使用中十分高效,在JDK1.8开始广泛使用
Lamda表达式和函数式接口编程是相辅相成的,只有使用函数式编程才使得Lamda表达式变得有意义
函数式接口的意义:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口(Runnable接口)
-
public interface Runnable { public abstract void run(); }
- 对于函数式接口,我们可以通过Lamda表达式来创建接口对象
一次使用类的定义演化过程
1.接口-实现类
interface iLambda{ //接口中默认就是抽象方法,即abstract修饰 void PrintLambda(); } class sonILambda implements iLambda { @Override public void PrintLambda() { System.out.println("接口--实现类使用"); } }
接口-实现类是我们使用最多的定义方式,它的生命周期是全局的,也就是不加特别的约束的话,不管在那个包内都可以通过new关键字实例化一个对象
但是对于只使用一次的类,就不需要这么麻烦,所以就衍生出了下一种定义方式
2.接口-静态内部类
//主方法所在类,启动类:Application public class StudyLambda { //静态内部实现类 static class sonILambdaStatic implements iLambda { @Override public void PrintLambda() { System.out.println("接口--静态实现类"); } } public static void main(String[] args) { sonILambdaStatic sonILambda = new sonILambdaStatic(); sonILambda.PrintLambda(); } }
静态内部实现类,首先它的类是静态的,并且它的位置位于我们的主方法的内部
这样的静态实现类依旧需要static关键字修饰,它和外部实现类差别并不是很大
主要是作用域,一个在全局,一个在类的内部
3.接口-局部内部类
public static void main(String[] args) { //局部内部类 class sonILambdaMian implements iLambda { @Override public void PrintLambda() { System.out.println("接口--局部内部类"); } } sonILambdaMian Lambda = new sonILambdaMian(); Lambda.PrintLambda(); }
局部内部类作用域就更小了,它的作用域甚至只在主方法内部,
由于我们的类只使用一次,这样的定义也是没问题的
4.接口-匿名内部类
public static void main(String[] args) { //局部内部类 class sonILambdaMian implements iLambda { @Override public void PrintLambda() { System.out.println("接口--局部内部类"); } } //匿名内部类 iLambda iLambda = new iLambda() { @Override public void PrintLambda() { System.out.println("接口--匿名内部类"); } }; iLambda.PrintLambda(); }
匿名内部类,没有类名称,必须借助接口或者父类
在使用到匿名内部类的时候,就和Lamda表达式很像了,匿名内部类把类体和创建写在了一起
对于这样的类,就可以使用Lamda表达式进行简化
5.Lamda表达式
//Lambda表达式 iLambda = ()->{ System.out.println("Lamda表达式"); }; iLambda.PrintLambda();
我们可以看到,有了Lamda表达式的,对于只使用一次的类的表达非常方便
Lamda表达式使用的规则:接口中只含有一个抽象方法
完整的五种实现类方法具体的位置
class sonILambda implements iLambda { @Override public void PrintLambda() { System.out.println("接口--实现类使用"); } } interface iLambda{ //接口中默认就是抽象方法,即abstract修饰 void PrintLambda(); } //主方法所在类,启动类:Application public class StudyLambda { //静态内部实现类 static class sonILambdaStatic implements iLambda { @Override public void PrintLambda() { System.out.println("接口--静态实现类"); } } public static void main(String[] args) { //局部内部类 class sonILambdaMian implements iLambda { @Override public void PrintLambda() { System.out.println("接口--局部内部类"); } } //匿名内部类 iLambda iLambda = new iLambda() { @Override public void PrintLambda() { System.out.println("接口--匿名内部类"); } }; //Lambda表达式 iLambda = ()->{ System.out.println("Lamda表达式"); }; iLambda.PrintLambda(); } }
Lambda表达式的优化
1.只有一句程序语句的程序体可以去掉大括号 { ...... }
iLambda = ()-> System.out.println("Lamda表达式");
2.对于有参数的方法可以不用参数类型
interface iLambda{ //接口中默认就是抽象方法,即abstract修饰 void PrintLambda(int a,String b); } iLambda = (a,b)-> System.out.println("Lamda表达式"+a+b);
3.对于只有一个参数可以小括号都不用带
interface iLambda{ //接口中默认就是抽象方法,即abstract修饰 void PrintLambda(int a); } iLambda = a-> System.out.println("Lamda表达式"+a);
上面的描述都是Lamda表达式的优化,对于常用的情况下,我们更推荐参数打上括号,语句体打上大括号
它即对多个参数的情况可以使用,但是又有简化,多条程序依旧也可以使用
iLambda = (a,b)-> { System.out.println("Lamda表达式"); System.out.println(a); System.out.println(b); };
如上写法,
就是比较标准的Lamda表达式的写法
二.线程的状态
线程一共有五个状态,其中线程的创建状态,已经在Lession1课程中说明完了
包括继承Thread类,实现Runnable接口等等
接下来我们会主要研究其它的四个状态
线程的停止:
线程的停止终究是不安全的,所以一般我们只推荐线程自然停止
线程的使用建议都是有穷的,而不是死循环
建议使用标志位停止线程,不建议使用jdk自带的一些停止线程的方法
public class TestStop implements Runnable{ private Boolean flag=true; public void setFlag(Boolean flag) { this.flag = flag; } @Override public void run() { int i=1; while (flag) { System.out.println("run" + i++); } } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 200; i++) { if (i==150){ testStop.setFlag(false); } System.out.println("main"+i); } } }
如上代码:
当我们的主函数执行到第150次的时候,就会设置flag为false
而我们的false就是用来停止其它线程的标志
这就是使用标志来逼停线程,但是最推荐的还是自然停止
线程休眠sleep():
- sleep(时间)指定当前线程的毫秒数;
- sleep存在异常捕获机制InterruptedException
- sleep时间到达后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每个对象都有一把锁,sleep不会释放锁
//模拟倒计时 public class TestSleep implements Runnable{ @Override public void run() { int i=20; while (i>=10){ try { Thread.sleep(1000); System.out.println(i); i--; } catch (InterruptedException e) { System.out.println("sleep"); } } } public static void main(String[] args) throws InterruptedException { TestSleep testSleep = new TestSleep(); new Thread(testSleep).start(); Thread.sleep(12000); for (int i = 9; i >0; i--) { System.out.println(i); Thread.sleep(1000); } } }
线程休眠简单来说就是让正在执行的线程程序停一会
它的停止时间单位是ms
1s = 1000ms
线程礼让yield():
- 礼让线程,让当前正在执行的线程停止,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功,可能cpu在礼让后又让它进来了
线程礼让的实现:
public class TestYield { public static void main(String[] args) { iYield iYield = new iYield(); new Thread(iYield,"线程A").start(); new Thread(iYield,"线程B").start(); } } class iYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行"); Thread.yield(); //线程礼让 System.out.println(Thread.currentThread().getName()+"结束执行"); } }
可能的两种情况:
正常礼让:
线程A开始执行
线程B开始执行
线程A结束执行
线程B结束执行
礼让失败:
线程A开始执行
线程A结束执行
线程B开始执行
线程B结束执行
线程的强制执行join():
线程的强制执行指的是,它在cpu中独占资源,一旦调用此方法,那么cpu就会优先处理这个线程,只有这个线程执行完成以后,其它线程才能执行
我们可以把它想象成平时排队时,那些插队的人,像是有特权一样,明明后来,结果它先被服务,才到我们
一旦调用强制执行线程,那么其它线程都会被阻塞
代码实现:
public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("join线程"+i); } } public static void main(String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); for (int i = 0; i < 500; i++) { if(i==200){ //强制执行线性 thread.join(); } System.out.println("main"+i); } } }
线程的状态观测:
线程状态,可以处于一下状态之一
NEW
- 尚未启动的线程处于此状态,其中也可以说是线程的新生状态
RUNNABLE
- 在Java虚拟机中执行的时候处于此状态,也可以叫运行时状态
BLOCKED
- 被阻塞等待监视器锁定的线程处于此状态,也可以叫阻塞状态
WAITTING
- 正在等待另一个线程执行特定动作的线程处于此状态,也可以叫阻塞状态
TIMED_WAITING
- 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态,也可以叫阻塞状态
TERMINATED
- 已退出的线程处于此状态,也可以叫死亡状态
一个线程可以在给定的一个时间段处于一个状态,但是它代表不了操作系统和JVM虚拟机的状态
Thread.State 类包含了线程的所有状态
代码简单描述:
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("sleep"); } } System.out.println("Run over"); }); // 观察状态 Thread.State state = thread.getState(); //线程未启动但被创建的状态 System.out.println(state); //启动后的状态 thread.start();//启动线程 state = thread.getState(); System.out.println(state); //RunTime while (state != Thread.State.TERMINATED){//只要线程不死亡,就一直输出状态 Thread.sleep(200); state=thread.getState(); //更新线程状态,必须做的事情 System.out.println(state); } }
注意:一旦线程死亡就不能再次通过start()方法启动了,而是要重新开辟线程,new Thread()
线程优先级Priority:
Java提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程
线程调度器按照线程的优先级决定应该先调度那个线程来执行
优先级调度的范围用数字表示 :1~10
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY = 10
- Thread.NORM_PRIORITY = 5
改变线程优先级的方法:
- setPriority(int xxx) 设置线程的优先级
- getPriority() 查看线程的优先级
线程的优先级设置一定要此线程启动前调用
public class TestPriority implements Runnable{ @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { System.out.println("sleep"); } System.out.println(Thread.currentThread().getName()+"---->"+Thread.currentThread().getPriority()); } public static void main(String[] args) { TestPriority testPriority = new TestPriority(); //拿到主线程的优先级 System.out.println(Thread.currentThread().getName()+"---->"+Thread.currentThread().getPriority()); //开辟新线程 t1 Thread t1 = new Thread(testPriority,"t1"); //先设置其优先级 t1.setPriority(1); //然后再启动线程 t1.start(); Thread t2 = new Thread(testPriority,"t2"); t2.setPriority(10); t2.start(); Thread t3 = new Thread(testPriority,"t3"); t3.setPriority(8); t3.start(); } }
注意点:
线程的优先级低,只能代表被cpu调度的概率低,并不是写定了谁的优先级最小就一定最后执行
我们只能根据更改优先级去干扰cpu的调度,并不能直接控制cpu的调度,详情参考《计算机操作系统》处理机章节
守护线程daemon:
- 线程分为用户线程和守护线程
- 虚拟机必须保证用户线程执行完毕 (main线程,主线程一定是执行完,程序结束)
- 虚拟机不必等到守护线程执行完毕 (GC就是典型的守护线程,不仅不可见,可能很多人都会忽略他的存在,垃圾收集器)
- 守护线程主要工作于:日志记录,监控内存,垃圾回收,等待操作
代码描述:
public class TestDaemon { public static void main(String[] args) { me i = new me(); hiddenLove hidden = new hiddenLove(); Thread isI = new Thread(i); isI.setDaemon(true); isI.start(); new Thread(hidden).start(); } } class me implements Runnable{ @Override public void run() { int i=0; while (true) { i++; System.out.println("喜且不语,急却等待:第"+i+"次"); } } } class hiddenLove implements Runnable{ @Override public void run() { int i; for (i = 1; i < 5 * 365; i++) { System.out.println("释放希望,沐浴阳光,带来治愈"); } System.out.println("会再见嘛?我的意思是"+i+"天后"); } }
守护线程和用户线程最大的区别在于对虚拟机的要求不同
如果虚拟机还在运行,守护线程也在运行
如果用户线程不运行了,虚拟机也会停止