欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 培训 > JavaEE 【知识改变命运】03 多线程(2)

JavaEE 【知识改变命运】03 多线程(2)

2024/11/30 8:35:38 来源:https://blog.csdn.net/2402_84062064/article/details/143995328  浏览:    关键词:JavaEE 【知识改变命运】03 多线程(2)

文章目录

  • 复习
    • 1.1 进程和线程的区别
    • 1.2 线程创建的方式
    • 1.3 两者创建的区别
  • 2 多线程
    • 2.1多线程的优势-增加了运行的速度
    • 2.2Thread类及常用的方法
      • 2.2.1常用见的构造方法
      • 2.2.2获取当前类的信息
      • 2.2.3Thread 的⼏个常⻅属性
        • 1 演示后台线程
        • 2 线程是否存活
        • 3 名称
        • 4 线程中断
        • 5 等待⼀个线程 - join()
        • 6 获取当前线程引⽤
        • 7 休眠当前线程
      • 2.3.4 线程的状态

复习

1.1 进程和线程的区别

  1. 进程是申请资源的最小单位
  2. 线程是cpu调度的最小单位
  3. 创建一个进程,里面会自动生成一个主线程,进程里面包含线程
  4. 进程与进程之间互不影响,线程与线程之间可能会造成影响,一个线程的崩溃会导致整个进程的结束。
    5 线程之间共享进程的资源

1.2 线程创建的方式

继承Thread类创建方式(线程对象),重写run()方法(线程执行的任务)
实现Runnable接口创建方式,重写run()方法(线程执行的任务)
其他三种:匿名类Thread,匿名Runnable,lambda表达式Runnable接口创建方式

1.3 两者创建的区别

因为java是单继承模式,所以当一个类继承了父类,就不能再继承Thread这个类了,Runnable接口式实现,解决了单继承的这种限制
接口式创建,有高内聚低耦合的特点,把线程类和要执行的代码块分开了,后面修改代码对程序的影响较小,

2 多线程

2.1多线程的优势-增加了运行的速度

场景1:进行两次自增10_0000_0000次的自增

    private static long count=10_0000_0000;public static void main(String[] args) {//串行方式serivalThread();//并行方式parallelThread();}private static void parallelThread() {long start = System.currentTimeMillis();Thread thread01 = new Thread(()-> {int a = 0;for(int i = 0; i <count; i++) {a += i;}});Thread thread02 = new Thread(()-> {int b = 0;for(int i = 0; i <count; i++) {b += i;}});thread01.start();thread02.start();try {thread02.join();thread01.join();//一定要加这个join等待,要不然算出来的时间只是创建一个线程的时间} catch (InterruptedException e) {throw new RuntimeException(e);}long end = System.currentTimeMillis();System.out.println("并行执行时间:"+(end-start));}private static void serivalThread() {long start = System.currentTimeMillis();int a = 0;for(int i = 0; i <count; i++) {a += i;}int b = 0;for(int i = 0; i <count; i++) {b += i;}long end = System.currentTimeMillis();System.out.println("串行执行时间:"+(end-start));} }

执行结果:
在这里插入图片描述
这里注意一下:
通过多线程的方式明显提升效率,并行的耗时是串行的一半多一点时间,多一点的时间是创建线程时候消耗的时间

场景2:我们把自加次数降为50万次 执行结果;
在这里插入图片描述
注意:并不是任何时候多线程的效率比单线程的高,当任务量很少的时候,单线程的效率可能会比多线程更高
因为创建线程本身就是也有一定的系统开销,这个开销没有进程的开销大,两个线程在cpu上面调度也需要一定的时间

2.2Thread类及常用的方法

2.2.1常用见的构造方法

在这里插入图片描述
创建线程对象
这里是引用
使用Runnable创建线程对象
在这里插入图片描述
创建线程对象,并命名
在这里插入图片描述
使用Runnable创建线程对象,并命名
默认的线程名Thread-N,N>=0;

    public static void main(String[] args) {Thread thread = new Thread(new Runnable(){@Overridepublic void run() {System.out.println("我的线程名:"+Thread.currentThread().getName());}},"Runnable线程");thread.start();} } ```执行结果: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c68e1eee57af43ed948b4cc0e3d53176.png)

2.2.2获取当前类的信息

public class Mian {public static void main(String[] args) {Thread thread = new Thread(()->{//获取类名String className = Mian.class.getName();System.out.println(className);//获取线程对象Thread thread1 = Thread.currentThread();System.out.println(thread1);//获取线程名String name = thread1.getName();System.out.println(name);String mName= thread1.getStackTrace()[1].getMethodName();System.out.println(mName);},"我是一个线程");thread.start();}
}

2.2.3Thread 的⼏个常⻅属性

在这里插入图片描述

  1. id:jvm层面的,jvm默认为Thread对象生成一个编号,和PCB区分开(操作系统层面) 名字:java层面
  2. 状态:java层面定义的状态
  3. 优先级:
  4. 是否后台线程:java层面,线程分为前台线程和后台线程,通过这个表示位来区分当前前台线程还是后台线程,关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
  5. 是否存活:PCB层的,表示系统中的PCB是否销毁,与Thread对应没啥关系 ,即简单的理解,为 run ⽅法是否运⾏结束了
  6. 是否被中断:通过设置一个标志位让线程执行时候判断是否要退出
  7. 名称:名称是各种调试⼯具⽤到
  8. 注意:Thread是java中的类-----创建Thread对象----->调用strat()方法----->JVM调用系统的API生成一个PCB----PCB与Thread对象一一对应
    Thread对象与PCB所处的环境不同,所以他们的生命周期也不同。
    public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(thread.getName()+ ": ID: " + thread.getId());System.out.println(thread.getName()+ ": 名称: " + thread.getName());System.out.println(thread.getName()+ ": 状态: " + thread.getState());System.out.println(thread.getName()+ ": 优先级: " + thread.getPriority());System.out.println(thread.getName()+ ": 后台线程: " + thread.isDaemon());System.out.println(thread.getName()+ ": 活着: " + thread.isAlive());System.out.println(thread.getName()+ ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(thread.getName()+ ": 状态: " + thread.getState());} }

在这里插入图片描述

1 演示后台线程
    public static void main(String[] args) {Thread thread = new Thread(new Runnable(){@Overridepublic void run() {while (true){System.out.println("hello myRunnable");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});// thread.setDaemon(true);thread.start();System.out.println("thread 是否存活"+thread.isAlive());System.out.println("main thread 已经结束");} }

在这里插入图片描述

  1. 子线程没有设置为后台线程,main线程结束,子线程不会结束 ,进程不结束在这里插入图片描述
  2. 子线程设置为后台线程,main线程结束,子线程会跟着结束,进程结束
  3. 补充:后台线程类似守护线程,main线程类似用户线程
  4. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  5. 守护线程:一般是为了工作线程服务的,当所有的用户线程结束守护线程自动结束,
  6. 常见的守护线程:垃圾回收机制
  7. 前台线程可以阻止进程结束
  8. 后台线程不能阻止进程结束
2 线程是否存活
InterruptedException {Thread thread = new Thread(new Runnable(){@Overridepublic void run() {{System.out.println("hello myRunnable");try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});System.out.println("thread 是否存活"+thread.isAlive());//mainthread.start();//子线程System.out.println("thread 是否存活"+thread.isAlive());//mainthread.join();// 保证PCB 已经结束System.out.println("thread 是否存活"+thread.isAlive());//main,虽然PCB结束但是子线程java层面的对象还在,因为生命周期不一样//System.out.println("main thread 已经结束");} }

在这里插入图片描述

3 名称
    public static void main(String[] args) {Thread thread = new Thread(()->{//获取类名String className = Mian.class.getName();//获取方法名对象Thread thread1 = Thread.currentThread();//获取线程名String name = thread1.getName();//获取线程的方法名String mName= thread1.getStackTrace()[1].getMethodName();System.out.println("当前类名"+className+"方法名"+mName+"()"+"线程名"+name);},"我是一个线程");thread.start();thread.setName("hha")//设置线程名称,使之与参数name相同} }

在这里插入图片描述
通过类+方法+线程的名称的方法 可以明确的记录某一个线程所产生的日志

4 线程中断
  1. 通过共享的标记来进⾏沟通
    private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了⼤事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");target.isQuit = true;} } ```
  1. 调⽤ interrupt() ⽅法来通知 使⽤ Thread.interrupted() 或者
    Thread.currentThread().isInterrupted() 代替⾃定义标志位.
    private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种⽅法均可以while (!Thread.interrupted()) {//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName()+ ": 有内⻤,终⽌交易!");// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了⼤事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");try {thread.interrupt();} catch (Exception e) {throw new RuntimeException(e);}} } ```

调用thread.interrput();方法时候
1:如果线程在运行状态,直接中断线程,不会报异常,符合程序预期
2:如果线程处于等待状态,就会报一个中断异常,要在异常处理代码块中段逻辑实现
在这里插入图片描述
当线程sleep状态时,执行中断操作,中断的是休眠状态的线程,就会抛出这个异常
在这里插入图片描述
3:PCB依然存在,任务继续执行
4. thread 收到通知的⽅式有两种:

  1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志 ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法.可以选择忽 略这个异常, 也可以跳出循环结束线程.
  2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过 ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到。
5 等待⼀个线程 - join()

在这里插入图片描述

public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName()+ ": 我还在⼯作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始⼯作");thread1.start();thread1.join();System.out.println("李四⼯作结束了,让王五开始⼯作");thread2.start();thread2.join();System.out.println("王五⼯作结束了");}}
6 获取当前线程引⽤

在这里插入图片描述
Thread.currentThread()在那个线程中调用获取的就是那个线程的对象。

7 休眠当前线程

在这里插入图片描述
因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。

2.3.4 线程的状态

线程的状态继承在一个枚举中
public class Main {public static void main(String[] args) {for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}

总共有六种状态:
在这里插入图片描述
NEW:表示创建好了一个java线程对象,安排好了任务,但是没有启动,没有调用start()方法之前是不会创建PCB的,和PCB没有关系
RUNNABLE::运行+就绪状态,在执行任务时候最常见的状态之一,在系统中有对应PCB
BLOCKED:等待锁状态,阻塞的一种
WATING:没有等待时间,一直死等,直到被唤醒 TIMED_WAIRING:指定了等待时间的阻塞状态,过时不候
TERMINATED:结束,完成状态,PCB已经销毁,但是java线程对象还在

在这里插入图片描述
在这里插入图片描述
有时侯会说有七种状态,是因为又把RUNNABLE又细分为两个状态:Ready(就绪)Running
其中的yield:线程的礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功

观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换

public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 1000_0000; i++) {}}, "李四");System.out.println(t.getName() + ": " + t.getState());;t.start();while (t.isAlive()) {System.out.println(t.getName() + ": " + t.getState());;}System.out.println(t.getName() + ": " + t.getState());;
}  ``

观察 2: 关注 WAITING 、 BLOCKED TIMED_WAITING 状态的转换

public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();} ```使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED

修改上⾯的代码, 把 t1 中的 sleep 换成 wait

public static void main(String[] args) {final Object object = new Object();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {while (true) {try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}, "t1");t1.start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {System.out.println("hehe");}}}, "t2");t2.start();} ``使⽤ jconsole 可以看到 t1 的状态是 WAITING

结论:
BLOCKED 表⽰等待获取锁, WAITING 和 TIMED_WAITING 表⽰等待其他线程发来通知. •
TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在⽆限等待唤醒
wait()是Object类里面的,Join()和sleep()是Thread类里面的。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com