Thread 类的基本用法
此时看线程的代码执行结果好像和普通代码的结果没什么区别 并没有很好体现出线程的用法和效果。给MyThread的输入加入循环看结果好像就只有MyThread在输出程序里:1.t线程在里CPU 时间几乎都被它抢走控制台被疯狂刷屏改良1.给MyThread循环中加入sleep可以看的main线程是有执行的改良2.main线程加入循环给两线程都加入sleep。
一、线程的创建
1.1 方法一 继承Thread类
继承Thread重写run 来创建一个线程类
class MyThread extends Thread{
@Override
public void run() {
System.out.println("This is Thread");
}
}
创建MyThread类的实例 调用start方法启动线程
public static void main(String[] args) {
//向上转型
Thread t = new MyThread();
//真正在系统中创建出一个线程
t.start();
System.out.println("this is main");
}

此时看线程的代码执行结果好像和普通代码的结果没什么区别 并没有很好体现出线程的用法和效果。
给MyThread的输入加入循环

看结果好像就只有MyThread在输出 但是结果并如此
程序里一共两个线程:1.main 线程 2.t 线程
实际情况是:
1.System.out.println("this is main");是执行了的,只执行了一次,但几乎看不到。2.子线程“刷屏”把 main 的输出淹没了
这是最核心的原因
t线程在while(true)里不休眠CPU 时间几乎都被它抢走
控制台被
This is Thread疯狂刷屏
改良1.给MyThread循环中加入sleep

可以看的main线程是有执行的
改良2.main线程加入循环

从这块可以看出线程的作用特点 普通程序进入死循环的时候是跳不出来的 start创建了一个新的线程,多了一个执行流,能够干活(这个代码就可以"一心两用" 同时做两件事)
给两线程都加入sleep

这俩线程,谁先执行,谁后执行,都有可能,无法预测(编写代码,也不能依赖这俩逻辑的执行顺序)
操作系统内核的调度器,控制的没法在应用应用程序中编写代码控制(调度器没有提供api 的)唯一能做的就是给线程设置优先级(但是优先级,对于操作系统来说,也是仅供参考,不会严格的定量的遵守)
方法一特点:1.简单直观 2.不能再继承其他类(Java 单继承)
1.2 方法二 实现Runnable 接口
实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
while (true){
System.out.println("this is thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
创建了一个 Runnable 任务对象,并将该任务交给 Thread 线程对象,由该线程在启动后执行该任务。
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
while (true){
System.out.println("this is main");
Thread.sleep(1000);
}
}

方法二特点:1.可以继续继承别的类 2.线程和任务解耦
1.3 方法三 使用匿名内部类
Thread t = new Thread(){
};
做了三件事
1.创建了一个Thread的子类。子类叫啥名字?不知道!!匿名!
2.(里面就可以编写子类的定义代码.子类里要有哪些属性,要有哪些方法重写父类的哪些方法.....
3.创建了这个匿名内部类的实例并且把实例的引用赋值给t.
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
while(true){
System.out.println("this is thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while (true){
System.out.println("this is main");
Thread.sleep(1000);
}
}

这样就可以少定义一些类了.一般如果某个代码是“一次性"就可以使用匿名内部类的写法。
1.4 方法四 使用Runnable 匿名内部类
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("this is thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t = new Thread(runnable);
t.start();
while (true) {
System.out.println("this is main");
Thread.sleep(1000);
}
}

1.5 方法⑤ 针对三和四进一步改进,引入lambda表达式
本质上就是一个"匿名函数最主要的用途,就是作为回调函数”

大部分推荐方法五的写法
二、Thread的常见构造方法

2.1Thread(Runnable target, String name)使用Runnable对象创建线程对象,并命名

2.2 Thread的几个常见属性
1.isDaemon() 是否后台线程
啥是后台线程 再看回上述代码的线程图分析

红色括起来的这些是JVM自带的线程,他们的存在 不影响进程结束(即使他们继续存在,如果进程要结束了,他们也随之结束了)
这种就是后台线程
而框起来的t1,t2,t3这几个线程的存在,就能够影响到进程继续存在这样的线程,就称为前台线程”

前台线程,后台线程,都是有多个的.如果有多个前台线程,必须得所有的前台线程都结束,进程才结束
2. isAlive() 是否存活
Java代码中创建的Thread对象,和系统中的线程,是一一对应的关系。但是,Thread对象的生命周期,和系统中的线程的生命周期,是不同的(可能存在,Thread对象还存活,但是系统中的线程已经销毁的情况)
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true){
System.out.println(t.isAlive());
Thread.sleep(1000);
}
}

3. isInterrupted() 是否被中断

三、线程中断
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()){
System.out.println("this is thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//情况一 这个线程掀桌了
//throw new RuntimeException(e);
//情况二 1.加入break就是立即终止
//break;
//情况三 2.啥都不写就是不终止
}
}
System.out.println("t 结束");
});
t.start();
Thread.sleep(3000);
System.out.println("main线程 尝试终止t线程");
t.interrupt();
}
1.情况一

线程一:输入一次休息一秒
线程二:等待3秒 3秒到了执行t.interrupt(把循环的false改成true
此时 JVM立刻检查一个特殊规则 JVM发现:t线程:正在 sleep interrupt flag == true
JVM规定:如果线程在 sleep/wait/join中并且被 interrupt必须立刻抛 InterruptedException
抛出异常线程中断
异常终止线程(不优雅)使用RuntimeException强制结束
违背了中断的设计初衷
2.情况2

与上述过程一样 不过在catch中 直接break跳出循环
3.情况3

catch什么都不做
1.interrupt设置中断标志while循环的条件 变成!true
2.JVM 发现线程正在 sleep根据JVM 规则:sleep 中被 interrupt - 抛 InterruptedException
抛异常前,会把 interrupt flag 清成false循环条件再次变成!false
3.进入catch块
啥也没干
针对上述代码
其实是sleep在搞鬼
正常来说。调用Interrupt-方法就会修改isInterruptted方法内部的标志位 设为true
由于上述代码中,是把sleep给唤醒了,这种提前唤醒的情况下,sleep就会在唤醒之后,把isInterruptted 标志位给设置回false
因此在这样的情况下,如果继续执行到循环的条件判定,就会发现能够继续执行
四、join() 线程等待
为什么需要线程等待呢?

多个线程之间 并发执行 随机调度.
站在程序员的角度,咱们不喜欢随机的东西join能够要求,多个线程之间,结束的先后顺序.
比如,在主线程中调用tjoin就是让主线程等待t线程先结束.虽然可以通过sleep休眠的时间,来控制线程结束的顺序但是,有的情况下,这样的设定并不科学
有的时候,就是希望,t先结束,main就可以紧跟着结束了,此时通过设置时间的方式,不一定靠谱
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 300; i++) {
System.out.println("this is thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程结束");
});
t.start();
//Thread.sleep(3000);
t.join();
System.out.println("main 结束");
}

在 main线程中,调用t.join效果:让main线程等待t先结束当执行到t.join,此时main线程就会"阻塞等待”一直等到t线程执行完毕,join才能继续执行只要t线程不结束,主线程的join就会一直一直的等待下去
join提供了带参数的版本,指定“超时时间"
等待的最大时间

如果3000之内,比如刚过了1000,t就结束了,此时join立即继续执行(不会等满3000)如果超过3000,t还没结束,此时join也继续往下走,就不等了
五、线程休眠
线程休眠其实在线程的时候就一直在使用了

六、获取线程实例
获取当前线程

获取指定线程

更多推荐


所有评论(0)