目录
[启动一个线程 start()](#启动一个线程 start())
[等待一个线程 join()](#等待一个线程 join())
启动一个线程 start()
java标准库/jvm提供的方法
本质上是调用操作系统的api
这样的方法没有实现,只有一个声明
native 本地方法
jvm是c++来写的
带有native的方法实现是在jvm内部通过c++代码来实现的
(要想看到这部分代码需要额外下载jvm的源码c++)
在c++的实现中内部就会判断当前是哪个操作系统,并且调用对应系统的api来创建线程
每个Thread对象都只能start一次,日抛
每次想创建一个新的线程,都得创建一个新的Thread对象(不能重复利用)
java中期望Thread对象和操作系统中的线程是一一对应
start是系统调用api
中断一个线程
核心:让线程的入口方法能够尽快结束
"终止"此处让线程直接就停止了不会再恢复了
让线程的入口方法执行完毕,线程就随之结束了
(run方法尽快return)
方式1.通过共享的标记来进行沟通
变量捕获
lambda里面希望使用外面的变量
触发"变量捕获"这样的语法
lambda是回调函数
执行时机是很久之后(操作系统真正创建出线程之后才会执行)
很有可能,后续线程创建好了。当前main这里的方法都执行完了,对应的isFinished就销毁了
为了解决上述 的问题
java的做法是,把被捕获的变量给拷贝一份,拷贝给lambda里面
外面的变量是否销毁就不影响lambda里面的执行了
拷贝,意味着,这样的变量就不适合进行修改
修改一方,另一方就不会随之变化的(本质上是两个变量)
这种一边变,一边不变可能给程序员带来更多困惑
java大佬们就想了个办法压根不允许你这里进行修改
把上述代码改成成员变量
此时不再是"变量捕获"语法
而是切换成"内部类访问外部类成员"语法
java
public class Demo10 {
private static boolean isFinished = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(!isFinished){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("Thread 结束");
});
t.start();
Thread.sleep(3000);
isFinished = true;
}
}
lamabda本质上是函数式接口
相当于一个内部类
isFinished变量本身就是外部类 (Demo10)的成员
成员变量生命周期也是让GC来管理的
在lambda里面不担心变量生命周期失效的
也就不必拷贝,也就不必限制final之类的
GC==垃圾回收
方式2.调用interrupt()方法来通知
线程终止
java的Thread对象中提供了现成的变量,直接进行判定,不需要自己创建了
java
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t =new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
System.out.println("t结束");
});
t.start();
Thread.sleep(3000);
System.out.println("main线程尝试终止t线程");
t.interrupt();
}
}
Thread.currentThread().isInterrupted()判断线程是否被终止了
t.interrupt();主动去进行终止
修改这个boolean变量的值
除了设置boolean变量(标志位)之外
还能够唤醒像sleep这样的阻塞方法
这个操作能够唤醒sleep
sleep会抛出异常
针对异常的处理,使用break结束循环
如果不加上述break(空着的)
针对上述代码
其实是sleep在搞鬼
正常来说,调用Interrupt方法就会修改isInterrupted方法内部的标志位设为true
由于上述代码中,是把sleep给唤醒了
这种提前唤醒的情况下sleep就会在唤醒之后,把isInterrupted标志位设置回false
因此在这样的情况下如果继续执行到循环的条件判定,就会发现能够继续执行
sleep这样设定之后相当于让程序员在catch语句中有更多的选择空间
程序员可以自行决定这个线程是要立即结束还是等会再结束还是不结束
加上break就是立即终止
啥都不写就是不终止
catch中先执行一些其他逻辑再break,就是稍后终止
java中线程的终止不是一个强制性的措施
不是main让t终止t就一定终止,选择权在t自己手上(看t线程自己的代码咋写)
java这里的设定,把决定权交给被终止的线程自己了
线程内部代码中就可以保证,一定是执行出一些"阶段性"的结果,然后才真正退出
上述这里抛出异常的方法不只是sleep会有
等待一个线程 join()
多个线程之间 并发执行 随机调度
join能够要求,多个线程之间,结束的先后顺序
比如。再主线程中调用t.join()
就是让主线程等待t线程先结束
java
public class Demo12 {
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);
}
}
System.out.println("t线程结束");
});
t.start();
t.join();
System.out.println("main线程结束");
}
}
在main线程中调用t.join
效果:让main线程等待t先结束
当执行到t.jion此时main线程就会"阻塞等待"
一直等到t线程执行完毕,join才能继续执行
不见不散,只要t不结束main的join就会一直等下去
join提供了带参数的版本,指定"超时时间"等待的最大时间
带有超时时间的等待才是更科学的做法
哪个线程调用这个方法返回哪个线程的引用(类似于this)
休眠当前线程
因为线程的调度是不可控的所以这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间
sleep
写了sleep1000很可能比1000略多一点
代码调用sleep相当于让当前线程让出cpu的资源
后续时间到了的时候需要操作系统内核把这个线程重新调到cpu上才能继续执行
时间到意味着允许被调度了
而不是立即就执行了
sleep(0)使用sleep的特殊写法
写了sleep(0)意味着让当前的线程放弃cpu资源(把cpu让出来给别人更多的执行机会,等待操作系统重新调度