多线程篇一
如笔者理解有误,欢迎交流指正⭐
线程
一个线程就是一个“执行流”.
每一个线程都是一个独立的执行逻辑.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class MyThread extends Thread { @Override public void run() { while (true) { System.out.println("Welcome Thread!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread thread = new MyThread(); thread.run(); while (true) { System.out.println("Hello Main!"); Thread.sleep(1000); } } }
|
start 和 run 都是 Thread 的成员.
run 只是描述了线程的入口(线程要做什么任务)
**start 则是真正调用了系统 API, 在系统中创建出线程, 让线程再调用 run.**(即实现并发【兵分两路 充分利用多核CPU】 Java中的“并发”多指并行+并发 区别操作系统中的宏观和微观理解)
为什么需要线程
好奇宝宝发问🤭
人多力量大!单核CPU能力有限,多核CPU可以提高算力,并发编程能充分利用多核CPU的资源.
我们是有礼貌的孩子!“IO等待”在有些场景无法避免,如何利用等待的时间做其他工作?当然少不了并发编程.
tips
多进程也能实现并发,但线程量级更轻.
1.创建线程比创建进程更快.
2.销毁线程比销毁进程更快.
3.调度线程比调度进程更快.
多线程
当进行复杂任务时,会创建多个线程进行问题处理,不同线程有不同的小任务排队执行.
操作系统中运行的任何程序都至少有一个主线程.
如何监测多线程运行情况
IDEA
debug启动有一个专门的窗口查看方法的调用栈,可以查看所有线程的信息.
下断点,右击可选择全部线程(ALL)或者指定的线程(Thread)
debug
Jconsole
新手推荐
启动前确保程序跑起来
Jconsole是JDK中自带的工具
Ctrl+Alt+shift+s打开project structure 查看jdk路径
如果双击打不开 可尝试管理员方式运行
线程和进程的区别
1.进程是包含线程的:每个进程至少有一个线程存在,即主线程.
2.进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.
3.进程是系统分配资源的最小单位,线程是系统调度的最小单位.
Thread.sleep()
线程休眠,方便观察,sleep是Thread的静态方法.
sleep方法可能会抛出如下异常
此异常为受查异常,必须显示处理
必须try cacth,不能throws
上述代码重写父类的run,父类的run方法没有throws,子类也不能有(开头线程处代码 可查源码)
sleep每秒钟打印出来的顺序也是不确定的“随机性”.
取决于操作系统对线程的调度模块,非等概率随机.
创建线程的其他写法【面试题】
非完全,后续进行补充~
继承thread 重写run(显示)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class MyThread extends Thread { @Override public void run() { while (true) { System.out.println("Welcome Thread!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread thread = new MyThread(); thread.run(); while (true) { System.out.println("Hello Main!"); Thread.sleep(1000); } } }
|
实现Runnable 重写run(显示)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class MyRunnable implements Runnable { @Override public void run() { while (true) { System.out.println("Welcome Thread!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Demo2 { public static void main(String[] args) { Runnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start();
while (true) { System.out.println("Hello Main!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
对比上面两种方法: 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用. 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()
继承Thread 重写run 使用匿名内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class Demo3 { public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { while (true) { System.out.println("Welcome Thread!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } };
thread.start(); while (true) { System.out.println("Hello Main!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
实现Runnable 重写run 使用匿名内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class Demo4 { public static void main(String[] args) {
Thread thread = new Thread(new Runnable() { @Override public void run() { while (true) { System.out.println("Welcome Thread!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread.start();
while (true) { System.out.println("Hello Main!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
使用Runnable和直接继承Thread之间的主要区别
解耦合(使用反射 各个功能代码不能交叉 编写封闭的代码)
模块间的关系越密切,耦合性越强,越独立.
比如我去汉堡店打工,负责做美味蟹黄堡,客人太多,Boss又招新人负责分担相同的工作,Boss电脑被黑了,员工里只有我掌握了相关技术,那就只有我能解决问题.
做美味蟹黄堡是一个任务,我做或者新员工做本质上没有区别.可以把此任务单独提取成Runnable,可以调整执行.
反入侵只有我能来做,此任务就是和我有耦合关系.
两个任务我都可以做,那就是超级加倍!(bushi)与店面正常运转这个任务的耦合度更高.
基于lambda表达式(推荐)
Java中不允许函数独立存在(其他语言函数叫 functioin java中叫method方法)
lambda 更简化的语法表示方式 “语法糖”【例如 for each for(int x: arr) 】
相当于匿名内部类的简单写法
lambda 本质上是一个匿名函数(函数式接口 未脱离类) 主要用来实现”回调函数“的效果
1 2 3
| Thread t = new Thread(() -> {
});
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Demo5 { public static void main(String[] args) { Thread t = new Thread(() -> { while (true) { System.out.println("Welcome Thread!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start();
while (true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
回调函数
是的 我们的皇帝遗诏(bushi)
非主动调用也非立即调用,在合适的时机进行执行.
通常由操作系统、库、框架、别人写的代码进行调用
Thread的常见构造方法
方法 |
说明 |
Thread() |
创建线程对象 |
Thread(Runnable taeget) |
使用 Runnable 对象创建线程对象 |
Thread(String name) |
创建线程对象,并命名 |
Thread(Runnable taeget,String name) |
使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group, Runnable target) |
线程可以被用来分组管理,分好的组即为线程组 |
创建线程的时候 可以去指定name
name不影响线程的执行 只是一个命名
方便后续调试区分
线程的入口方法执行结束 线程就被销毁
对于主线程来说 入口方法就是main方法 (例如上述t.start一瞬间就执行完毕)
Thread的几个常见属性
属性 |
获取方法 |
ID |
getId() |
名称 |
getName() |
状态 |
getState() |
优先级 |
getPriority() |
是否后台线程 |
isDaemon() |
是否存活 |
isAlive() |
是否被中断 |
isInterrupted() |
getId()
线程的身份标识 标识一个进程中唯一的一个线程
(这个id式Java给这个线程分配的 不是系统api提供的线程id 更不是pcb中的id)
getPriority()
虽然提供api 可以获得/设置优先级 但很难被察觉
优先级影响到的是微观上进行的调度
isDaemon()
守护线程(后台线程)
不结束不影响整个进程的结束
前台线程
一个Java进程中 若前台线程未执行结束 此时整个进程是一定不会结束的
1 2 3 4
| t.setDaemon(true);
t.start();
|
默认情况下一个线程是前台线程,未处理的线程默认为前台线程
改成后台线程之后 主进程飞快的结束 此时没有其他前台线程 进程结束 t来不及执行就完了
isAlive()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Demo7 { public static void main(String[] args) { Thread t = new Thread(() -> { System.out.println("线程开始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程结束"); });
System.out.println(t.isAlive()); t.start(); System.out.println(t.isAlive()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(t.isAlive()); } }
|
判定内核中的线程是不是被销毁了(回调方法执行完毕 线程就结束了)
Thread对象的生命周期 比系统内核中的线程更长一些
Thread对象还在 内核中的线程已经被销毁
线程并发执行,并发顺序由系统的调度器决定,不确定.(极端情况可能主线程卡新线程先打印)
未完待续❀❀❀