Java-多线程-2
学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1V4411p7EF
学习博客:csdn – https://blog.csdn.net/yalu_123456/article/details/91049333?spm=1001.2014.3001.5501
4、静态代理模式
静态代理模式总结
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
好处:
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
静态代理模式中,真实对象和代理对象都要实现同一个接口。 在这里的例子中 都去实现接口中的 happyMarry()方法。
那么需要对 真实对象实现接口类, 重写其中的方法。
代理对象实现接口类, 在这其中它的属性需要有你这个真实对象,然后才能代理; 在实现接口的方法时, 它不仅仅能够帮你实现 你要做的事情,即this.you.happyMarry(); //你要结婚; 还能增加 附带 一些真实对象不能做的事情。这里的是 before() 和 after() 方法 事情。
然后这两个实现接口类, 代理对象和真实对象 在代理模式中创建对象,完成具体的静态代理。
例子:
package com.AL.Multithread;
//静态代理模式总结
// 真实对象和代理对象都要实现同一个接口
// 代理对象要代理真实角色
// 好处:
// 代理对象可以做很多真实对象做不了的事情
// 真实对象专注做自己的事情
public class StaticProxy {
public static void main(String[] args) {
// 代理对象 代理 真实的对象
You1 you1 = new You1();
you1.happyMarry();
new WeddingCompany(you1).happyMarry();
}
}
// 定义共同的接口: 结婚
// 真实对象和代理对象都要实现同一个接口, 你和婚庆公司都要实现结婚
interface Marry{
void happyMarry();
}
// 真实对象: 你 你要实现结婚 接口
class You1 implements Marry{
@Override
public void happyMarry() {
System.out.println("我要结婚了, 我好慌!!!");
}
}
//代理对象:婚庆公司
class WeddingCompany implements Marry{
//婚庆公司需要有你这个人。 代理对象需要有一个真实的对象
private You1 you;
public WeddingCompany(You1 you) {
this.you = you;
}
@Override
public void happyMarry() {
before();
this.you.happyMarry(); //你要结婚
after();
}
private void before() {
System.out.println("结婚之前,布置酒席");
}
private void after() {
System.out.println("结婚之后,催你收钱");
}
}
结果:
我要结婚了, 我好慌!!!
结婚之前,布置酒席
我要结婚了, 我好慌!!!
结婚之后,催你收钱
5、Lambda表达式
Lambda表达式
- 避免匿名内部类定义过多
- 其实质属于函数式编程的概念
- 可以让代码看起来很简洁
- 去掉了一堆没有意义的代码,只留下核心的逻辑
函数式接口
Functional Interface(函数式接口)
定义:
- 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
- 对于函数式接口,可以通过lambda表达式来创建该接口的对象。
5.1、Lambda表达式的推导:
只有对于函数式接口,即只有一个方法的接口, 才能使用 Lambda表达式 去创建接口的对象。
例子1:
package com.AL.Multithread;
/**
* 推导 Lambda 表达式
*/
public class TestLambda1 {
public static void main(String[] args){
ILike like = new Like(); //这个新建的对象,函数式接口,且继承了类 Like
like.lambda(); //Like中的lambda 方法
ILike like2 = new Like2();
like2.lambda();
//4. 局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("我们的恋爱是对生命最大的浪费!");
}
}
ILike like3 = new Like3();
like3.lambda();
// 5. 匿名内部类。 没有类的名称, 必须要借助接口或父类
ILike like4 = new ILike(){
@Override
public void lambda() {
System.out.println("我们结婚吧!!!");
}
};
like4.lambda();
// 6. 用lambda简化
like = ()->{
System.out.println("都是你的错!!!");
};
like.lambda();
}
// 3.静态局部类. 创建的是一个静态的局部 类 class
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like fly");
}
}
}
// 1.定义一个函数式接口.
// 对于函数式接口,可以通过lambda表达式去创建这个接口的对象
interface ILike{
void lambda();
}
// 2.实现类
class Like implements ILike{
@Override
public void lambda() { //这个方法的快捷键 ALT+INSERT
System.out.println("I like lambda");
}
}
结果:
I like lambda
I like fly
我们的恋爱是对生命最大的浪费!
我们结婚吧!!!
都是你的错!!!
例子2:
package com.AL.Multithread;
public class TestLambda2 {
public static void main(String[] args){
ILove love = new Love();
love.love(1);
ILove love2 =new Love2();
love2.love(2);
//局部内部类
class Love3 implements ILove{
@Override
public void love(int a) {
System.out.println("不爱我就拉倒--"+a);
}
}
ILove love3 = new Love3();
love3.love(3);
//匿名内部类
ILove love4 = new ILove(){
@Override
public void love(int a) {
System.out.println("放学别走啊--"+a);
}
};
love4.love(4);
//lambda 表达式
ILove love5 = (int a)->{
System.out.println("give me an mojito!!-->"+a);
};
love5.love(5);
}
//静态局部类
static class Love2 implements ILove{
@Override
public void love(int a) {
System.out.println("我爱你吗?-->"+a);
}
}
}
//函数式接口。 只有一个抽象方法
interface ILove{
void love(int a);
}
//实现类
class Love implements ILove{
@Override
public void love(int a) {
System.out.println("I love you -->"+a);
}
}
结果:
I love you -->1
我爱你吗?-->2
不爱我就拉倒--3
放学别走啊--4
give me an mojito!!-->5
对于具有多个参数类型的函数式接口:
package com.AL.Multithread;
public class TestLambda3 {
public static void main(String[] args) {
YouLove youLove = (a, b) -> {
System.out.println("我说了:"+a+b);
};
youLove.youLove(3,5);
}
}
interface YouLove {
void youLove(int a, int b);
}
结果:
我说了:35
总结:
- lambda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹
- 前提是接口为函数式接口。即接口里面只有一个方法。
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。
5.2、Lambda表达式在多线程中的应用
package com.AL.Multithread;
//lambda表达式在多线程中的使用
public class TestLambda4 {
public static void main(String[] args) {
//原先方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("你讲咩啊");
}
}).start();
//线程体只有一行可以省略到极致
new Thread(()-> System.out.println("你讲咩啊讲")).start();
//如果线程体有多行 , 用一个代码块包裹起来就好.
new Thread(()-> {
for (int i = 0; i < 40; i++) {
System.out.println("你说啥?");
}
}).start();
}
}
6、线程状态
6.1、线程状态
- 线程在创建 新建 new 的时候就进入到了新生状态;
- 在调用start()方法的时候,线程就立即进入了就绪状态。 但这时候需要等待cpu调度,才能去进行执行;
- 当cpu对已经就绪状态的线程进行调度执行时,会进入运行状态;
- 进入运行状态时,线程才开始真正执行线程体的代码块
- 当在运行状态时, 调用了 sleep,wait或同步锁定时,线程进入阻塞状态,这个时候,代码不会往下继续执行; 当阻塞状态解除后重新进入就绪状态,然后等待 CPU 的调度执行;
- 当在运行状态,等待线程执行完毕, 就会进入死亡状态,无法再次启动。
线程方法:
- setPriority(int newPriority) 更改线程的优先级
- static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
- void join() 等待该线程终止
- static void yield() 暂停当前正在执行的线程对象,并执行其他线程
- void interrupt() 中断线程,别用这个方式
- boolean isAlive() 测试线程是否处于活动状态
6.2、线程停止
停止线程:
不推荐使用JDK提供的 stop()、destroy()方法。【已废弃】
推荐线程自己停止下来
建议使用一个标志位进行终止变量
当flag=false,则终止线程运行。
例子: 我们测试一个使线程正常停止的代码,在这里,我们利用一个标志位进行终止线程的运行。
在这里我们重写了 run()方法, 然后调用 start()方法使线程进入就绪状态,然后等待 cpu调度执行。 此时多线程之间交替执行, 直到次数达到900 并改变相应的标志位, 去终止线程的运行。
package com.AL.Multithread;
// 测试 stop
// 1.建议线程正常停止---利用次数,不建议使用死循环
// 2.建议使用标志位--设置一个标志位,来决定stop
// 3.不要使用JDK中提供的 stop、destory方法,
public class TestStop implements Runnable{
// 1.设置一个标志位 flag
private Boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("IG go"+ i++);
}
}
// 2.设置一个公开的方法去停止线程,改变标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args){
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+ i);
if (i==900){
// 调用stop方法,改变标志位的值,让线程停止
testStop.stop();
System.out.println("IG 要输了");
}
}
}
}
结果: 我们可以看到在调用 start()方法后, 多线程之间交替执行(表现在 run方法和输出的main在交替打印输出),在i==900的时候,调用了stop方法去改变标志位,令线程终止运行,最终线程停止。
main0
IG go0
main1
IG go1
main2
...
main900
IG 要输了
main901
...
main998
main999
6.3、线程休眠
线程休眠
sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException
sleep时间到达后线程进入就绪状态
sleep可以模拟网络延时,倒计时等。
每一个对象都有一个锁,sleep不会释放锁。
例子:模拟网络延时。
模拟网络延时的作用是 放大问题的发生性。 如放大多线程时的并发性。
sleep(time) 其中的表示当前线程阻塞的毫秒数, 我们模拟延时200毫秒。在这里,我们实现Runnable接口类去创建多线程, 重写 run()方法,每次买票时票数-1;调用 start()方法,线程进入就绪状态,等待cpu调度; 进入运行后,线程运行,等待sleep 休眠进入阻塞状态, 再次进入就绪状态, 运行, 最终死亡,线程结束。
package com.AL.Multithread;
// 模拟网络延时
public class TestSleep implements Runnable {
// 票数
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums <= 0){
break;
}
// 添加模拟延时。 可以去放大 问题的发生性
try {
Thread.sleep(200); //200毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ ticketNums--+"票");
}
}
public static void main(String[] args){
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"小红").start();
new Thread(ticket,"黄牛党").start();
}
}
结果: 模拟延时放大问题的发生性, 在这里多线程的并发性: 票数有重复的,数据紊乱。
黄牛党拿到了第8票
小红拿到了第10票
小明拿到了第9票
小明拿到了第7票
小红拿到了第5票
黄牛党拿到了第6票
小红拿到了第4票
黄牛党拿到了第3票
小明拿到了第4票
黄牛党拿到了第1票
小明拿到了第0票
小红拿到了第2票
模拟倒计时的例子: 倒计时1秒,打印输出系统当前时间。
package com.AL.Multithread;
import java.text.SimpleDateFormat;
import java.util.Date;
// sleep() 模拟倒计时
public class TestSleep2 {
public static void main(String[] args){
// 打印系统当前时间
Date startTime = new Date(System.currentTimeMillis()); //获取系统当前时间
while (true){
try{
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis()); //更新当前的系统时间
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 模拟倒计时
public static void tenDown() throws InterruptedException{
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0){
break;
}
}
}
}
结果:
21:53:43
21:53:44
21:53:45
21:53:46
21:53:47
7、线程方法
7.1、线程礼让
线程礼让:
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看CPU心情。
测试线程礼让. 使用 Thread.yield()方法。例子:
package com.AL.Multithread;
// 测试线程礼让. 使用 Thread.yield()方法
// 礼让线程不一定成功, 看CPU的心情进行调度.
public class TestYield {
public static void main(String[] args){
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield(); // 礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
7.2、线程强制执行
join:
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
可以想象成插队。
在进行 join 使线程强制执行时,即先将此线程,插队进去的执行完, 然后再执行其它线程。其它线程堵塞。
例子:
package com.AL.Multithread;
//测试 join 方法, 可以想象成插队。
// 先将此线程,插队进去的执行完, 然后再执行其它线程。其它线程堵塞
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程的爸爸来了"+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 < 1000; i++) {
if (i==200){
thread.join(); //插队了
}
System.out.println("main"+ i);
}
}
}
7.3、观测线程状态
Thread State: 5个状态,新生、就绪、运行(阻塞)、死亡。
线程状态,线程可以处于一下状态之一:
new :尚未启动的线程处于此状态
Runnable :在java虚拟机中执行的线程处于此状态
Blocked :被阻塞等待监视器锁定的线程处于此状态。
Waiting :正在等待另一个线程执行特定动作的线程处于此状态。
Timed Waiting :正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
Terminated :已退出的线程处于此状态。这个表示线程已经终止了
一个线程可以给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
例子:
package com.AL.Multithread;
// 观察测试线程的状态
// 线程的5个状态: 新生、就绪、运行、阻塞、死亡
public class TestState {
public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("IG 给我冲啊!!!");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state); // NEW 新生
// 观察启动后
thread.start();
state = thread.getState();
System.out.println(state); //Run
// Terminated 已退出的线程处于此状态
while (state != Thread.State.TERMINATED){ //只要线程不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState(); //更新线程状态
System.out.println(state); //输出状态
}
}
}
结果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
IG 给我冲啊!!!
TERMINATED
注意:线程中断或者结束的时候,一旦进入死亡状态,就不能再次启动。 线程只能运行一次。
7.4、线程的优先级
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
先设定优先级,然后再启动。
需要注意的是:优先级低也会存在着被调用,这主要是看CPU的调度。
线程的优先级用数字表示,范围从1~10。
使用以下方式改变或获取优先级: getPriority(). setPriority(int xxx)
测试线程优先级的例子:
package com.AL.Multithread;
// 测试线程的优先级
// 需要注意的是:优先级低也会存在着被调用,这主要是看CPU的调度
public class TestPriority {
public static void main(String[] args){
// 主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
// 先设置优先级, 再启动
// 优先级的范围是: 1-10
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY); //MAX_PRIORITY=10
t4.start();
t5.setPriority(-1);
t5.start();
t6.setPriority(12);
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
结果:
main-->5
Thread-3-->10
Thread-0-->5
Thread-1-->1
Thread-2-->4
Exception in thread "main" java.lang.IllegalArgumentException
报错的原因是: 线程的优先级范围是 1- 10
修改后就可以正常运行了:
t5.setPriority(2);
t5.start();
t6.setPriority(10);
t6.start();
结果:
main-->5
Thread-3-->10
Thread-5-->10
Thread-4-->2
Thread-0-->5
Thread-2-->4
Thread-1-->1
7.5、守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如,后台记录操作日志,监控内存,垃圾回收等待。。。
不过守护线程结束的时候, 即虚拟机关闭 需要一定时间。
测试守护规则。 Thread.setDaemon(true); 此方法中默认为 false 表示用户线程,正常的线程都是用户线程。、我们修改为 true 使对应的线程为守护线程。
例子:
package com.AL.Multithread;
// 测试守护线程
// 上帝守护你 虚拟机必须确保用户线程执行完毕, 虚拟机不用等待守护线程执行完毕
public class TestDaemon {
public static void main(String[] args){
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); // 默认为 false 表示用户线程,正常的线程都是用户线程
thread.start();
new Thread(you).start(); //你 用户线程启动
}
}
// 上帝
class God implements Runnable{
@Override
public void run() { //快捷方式 CTRL+I
while (true){
System.out.println("上帝永远守护你");
}
}
}
//你自己
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("你要当一辈子的废物,还是一秒钟的英雄");
}
System.out.println("======物资不行啊!!!========");
}
}
部分结果: 虚拟机会确保用户线程执行完毕,即此时确保了 You 的Runnable接口实现类这个用户线程执行,直到for循环结束; 而不管守护线程运行是否结束。
因为这里默认守护线程应该是一直执行下去的,但是已经结束了, 这说明虚拟机不理会守护线程是否执行完毕,会自行在用户线程执行完毕后,自己关闭。
======物资不行啊!!!========
上帝永远守护你
上帝永远守护你
上帝永远守护你
上帝永远守护你
上帝永远守护你
上帝永远守护你