JavaEE初阶Day 4:多线程(2)

Day4:多线程(2)

1. catch语句

catch语句中有两种代码

  • throw new RuntimeException(e);:继续再抛出新的异常
  • e.printStackTrace();:打印异常调用栈

第一个是新版idea自动生成的,第二个是老版idea自动生成的,当前阶段使用哪个写法都可以,没有太大区别

但是catch中应该写什么,在实际开发中,是应该自定义的,都是规划好的,如下:

​ 1)可能会打印一些日志,把出现异常的详情都记录到日志文件中
​ 2)触发一些重试类的操作
​ 3)触发一些"回滚"类的操作
​ 4)触发一些报警机制(而是给程序员发短信/微信/打电话 告诉程序员你程序出问题了)

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

2. sleep的处理

在main方法中处理sleep,可以throws,也可以try catch,但是在线程的run中就只有一个选择了,只能try catch

  • throws也是方法签名的一部分,方法签名包含了方法名字、方法的参数列表、声明抛出的异常,不包含返回值、public/private,在方法重写的时候,要求方法签名是一样的,然而父类的run方法并没有抛出异常,所以重写的时候,就无法抛出异常了

3. Thread

3.1 Thread构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用Runnnable对象创建线程对象,并命名
Thread(ThreadGroup group, Runnable target)【了解】线程可以被用来分组管理
  • Thread(String name):可以在创建线程的时候,给线程起个名字,是否起名字,对于线程本身的运行效果,是没有任何影响的,但是起名字有个好处,Java进程运行过程中,可以通过工具(jconsole/IDEA)看到每个不同线程的名字,出现问题的时候,更直观的把问题的线程和代码关联起来(方便调试)
  • Thread(ThreadGroup group, Runnable target):开发中很少用到,有的时候,希望把多个线程进行分组,分组之后,就可以针对不同的组批量进行控制,这种写法目前实际开发中更多的是被线程池取代了
package thread;

public class Dmeo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            
            while (true){

                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            
        },"自定义线程");
        
        
        t.start();
        //至此main方法结束,意味着main线程就结束了(销毁了)
    }
}

在这里插入图片描述

这里没有看到main线程,一个进程启动,肯定得先有main线程调用main方法。注意,此处不是main线程没有被创建,而是执行太快,执行完毕了!main线程是JVM通过C++代码创建出来的,没有通过Java中的Thread类创建,就不会重写run

3.2 Thread的属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
3.2.1 ID

这里的ID和系统中PCB上的ID是不同的,是JVM自己搞得一套ID体系,Java代码无法获取到PCB的ID,虽然是一一对应的,但是编号不是一个体系

3.2.2 优先级

虽然Java提供了优先级接口,实际上就算修改了优先级,现象也不明显,自己修改了优先级是一回事,系统调度又是另一回事,这里的优先级只能是一个建议参考,具体还是要以系统自身为准,通过C调用系统原生API修改某个PCB里的优先级也是没有明显现象的

3.2.3 后台线程
  • 前台线程指的是,这样的线程如果不运行结束的话,Java进程是一定不会结束的;后台进程指的是,这样的线程,即使继续在执行,也不能阻止Java进程结束,前台线程可以有多个,直到最后一个前台线程结束,进程才会结束
  • 在Java代码中,main线程就是前台线程,程序员创建出来的线程,默认情况下都是前台线程,可以通过setDaemon方法来把线程设置为后台线程,在jconsole中看到的JVM中包含一些其他的内置的线程,就属于后台线程了,比如有的线程负责进行gc(垃圾回收),gc是要有周期性持续性执行的,不可能主动结束,要是把他设为前台,进程就永远都结束不了了
package thread;

public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        //设置为后台线程,必须设置在start之前,开弓没有回头箭
        t.setDaemon(true);

        t.start();
        //此时进程中只有main是前台线程了,只要main结束,整个进程就结束了,main执行完start立即结束了,此时t还没来得及打印,进程结束了,里面的线程自然随之结束
    }
}

注意:此处也是有一定概率出现t打印一次,然后结束进程的情况,这个事情就看是main先执行结束,还是t先执行一次打印(线程之间是抢占式执行,调度顺序不确定)

3.2.4 存活

指的是系统中的线程(PCB)是否还存在,Thread对象的生命周期和PCB的生命周期是不一定完全一样的

package thread;

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        t.start();
        Thread.sleep(1000);
        System.out.println(t.isAlive());//true
    }
}

上述写法会导致县城还没有执行完毕,但是t指向的对象就要被GC回收了

3.2.5 start

start真正创建线程(在内核中创建PCB)

  • 一个线程需要先通过run/lambda把线程要完成的任务,定义出来,start才是真正创建线程,并开始执行
  • 核心就是是否真的创建线程出来,每个线程都是独立调度执行的,相当于整个程序中多了一个执行流
  • 一个Thread对象,只能start一次,要想再搞另一个线程,就需要创建另一个Thread对象
package thread;

public class Demo9 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello t");
        });

        Thread t2 = new Thread(()->{
            System.out.println("hello t2");
        });

        t.start();
        t2.start();
    }
}
3.2.6 中断

终止线程,在Java中,都只是“提倡、建议”,真正要不要终止,还得线程本体来进行决定

t线程正在执行,其他线程,只能提醒一下t是不是要终止了,t收到这样的提醒之后,也还是得自己决定的

系统原生的线程中,其实有办法让别的线程被强制终止的,这种设定,其实不太好,所以Java没有采纳过来

线程之间调度是随机的,万一线程正在做一个很重要的工作,干了一半,强制结束可能引起一些bug

3.2.6.1 控制线程结束代码

核心思路:让需要终止的线程的入口方法尽快执行结束(跳出循环,还是尽快return都无所谓)

package thread;

public class Demo10 {
    
    private static boolean isRunning = true;
    public static void main(String[] args) {
        
        Thread t = new Thread(()->{
            // boolean isRunning = true;

            while (isRunning){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t线程结束了");
        });
        
        t.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        
        //3s后,主线程修改isRunning的值,从而通知t结束
        System.out.println("控制t线程结束");
        isRunning = false;
    }
}

如果是// boolean isRunning = true;会出现编译出错

变量捕获:作为lambda或者匿名内部类,都能捕获到外面一层作用域中的变量名,就可以使用,变量捕获有一个前置条件,就是要求得是final或者事实final,即没有人修改其变量的值

然而使用private static boolean isRunning = true;:原理是内部类访问外部类的成员,lambda本质上是一个匿名内部类,实现了函数式接口

3.2.6.2 interrupt和isInterrupted
package thread;

public class Demo11 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(10000);//10s,但是会唤醒sleep抛出异常
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        t.interrupt();
    }
}

Thread.currentThread().isInterrupted()

  • currentThread():是一个静态方法,这个方法能够获取到当前线程,获取到t这个引用
  • isInterrupted():线程内置的标志位,boolean变量,true表示线程要终止了,false表示线程要继续执行

t.interrupt();

  • 通过这个方法,就相当于是设置boolean值为true
  • 除了能设置boolean值,还可以唤醒sleep等阻塞方法,即使正在sleep(10s),刚休眠1s
    • 第一种写法,必须等待9s,才能让线程结束(sleep结束了,才能继续进行循环判定)
    • 第二种写法,则立即就会让sleep抛出一个InterruptedException异常,不会再等待,立即就唤醒了

当使用Interrupt方法之后,此时要不要结束,都是t线程自己决定的

当进行了t.interrupt();之后,修改了标志位,但是由于sleep的存在,如果代码没有sleep,确实是直接修改了标志位就结束了,但是有sleep的时候,触发Interrupt的时候,线程正在sleep,sleep被唤醒(进入catch中)的同时,就会清除刚才的标志位(又改回false),之所以这样做是为了要让程序员自己决定要不要结束,是继续执行,还是要立即结束,还是要等会结束

//1.程序员认为继续执行
while (!Thread.currentThread().isInterrupted()){
    System.out.println("hello thread");
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {

    }
}

//2.立即结束
while (!Thread.currentThread().isInterrupted()){
    System.out.println("hello thread");
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        break;
    }
}

//3.等会结束
while (!Thread.currentThread().isInterrupted()){
    System.out.println("hello thread");
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        //写一些逻辑之后,再break
        break;
    }
}
  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖了你都蹲不下来撸猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值