1.创建线程的几种常见的写法
1.创建一个类继承Thread,重写run(这个写法,线程和任务内容是绑定在一起的)
class MyThread extends Thread{
@Override
public void run(){
while(true){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 创建一个线程
// Java 中创建线程, 离不开一个关键的类, Thread
// 一种比较朴素的创建线程的方式, 是写一个子类, 继承 Thread, 重写其中的 run 方法.
Thread t=new MyThread();
t.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.创建一个类,实现Runnable接口,重写run.
此处创建的Runnable,相当于定义了一个任务(代码要干嘛),还是需要Thread实例把任务交给Thread,还是调用start来创建具体的线程
这个写法,线程和任务是分离开的(更好的解耦合)
模块/代码之间,关联关系越紧密,就认为耦合性越高,关联关系越不紧密,认为耦合性越低(写代码是要追求低耦合,高内聚)
class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
Thread t=new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1VidjJeR-1661760136664)(…/md文件图片备份/image-20220729155530456.png)]
问:为什么Thread,Runnable,InterruptedException都不需要import?(啥样的类,不需要import就能直接使用)
答:要么类在同一个包里,要么这个类是在java.lang里(lang是被默认导入了)
3.仍然是使用继承Thread类,但是不再显式继承,而是使用“匿名内部类”
Thread实例是Java中对于线程的表示,实际上要真的跑起来,还是需要操作系统里面的线程
public class Demo3 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
}
4.使用Runnable,是匿名内部类的方式使用
public class Demo4 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
}
}
5.使用lambda表达式,来定义任务(推荐做法)
public class Demo5 {
public static void main(String[] args) {
Thread t=new Thread(()->{
while(true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
lambda表达式本质上就仅仅是一个匿名函数(没有名字的,只使用一次的函数)
实际上,线程创建还有其他的创建方式,基于Callable/FutureTask的方式创建,基于线程池的方式创建…
一个经典的面试题:Java中创建线程有哪些方式?
答:至少7种
2.线程中断
isInterrupted()
启动一个线程start()
- 创建Thread实例,并没用真的在操作系统内核里创建出线程
- 调用start,才是真正在系统里创建出线程,才真正开始执行任务
线程的执行结束:只要让线程的入口方法执行完了,线程就随着结束了(主线程的入口方法,就可以视为main方法)
对应的,所谓的“中断线程”就是让线程尽快把入口方法执行结束
法一:直接使用自己的标志位来区分线程是否要结束
public class Demo9 {
// 用一个布尔变量表示线程是否要结束.
// 这个变量是一个成员变量, 而不是局部变量
private static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
System.out.println("新线程运行中");
while(!isQuit){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("新线程执行结束");
});
t.start();
Thread.sleep(5000);
System.out.println("控制线程退出");
isQuit=true;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fsvExMrh-1661760263272)(…/md文件图片备份/image-20220730222130022.png)]
法二:使用Thread自带的标志位
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("线程运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// e.printStackTrace();
// [1] 立即退出
break;
// System.out.println("新线程即将退出!");
// [2] 稍后退出, 此处的 sleep 可以换成任意的用来收尾工作的代码
// try {
// Thread.sleep(3000);
// } catch (InterruptedException ex) {
// ex.printStackTrace();
// }
// break;
// [3] 不退出! 啥都不做, 就相当于忽略了异常.
}
}
System.out.println("新线程已经退出");
});
t.start();
Thread.sleep(5000);
System.out.println("控制新线程退出!");
t.interrupt();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ynhJa1r-1661760263273)(…/md文件图片备份/image-20220730223921322.png)]
注意理解interrupt方法的行为:
- 如果t线程没有处在阻塞状态,此时interrupt就会修改内置的标志位
- 如果t线程正再处于阻塞状态,此时interrupt就让线程内部产生阻塞的方法,例如sleep抛出异常InterruptedException
这里解释一下这两句话的意思,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBaKRZ14-1661760263274)(…/md文件图片备份/image-20220731183518086.png)]
这里的线程里由于有sleep方法,让线程内部产生阻塞方法也就是sleep抛出异常,然后被catch捕捉到,执行catch里面的代码,正是这样的捕获操作,程序员就可以自行控制线程的退出行为了
- 如果你想立即退出,在catch里加个break,就退出了;
- 如果你想等一会再退出,你可以在catch写代码,最后写break,这样执行完后也能退出
- 如果你不想退出,就不加break,这样执行完catch后,还是会回到while进行判断,但是由于interrupt并没有修改内置标志位,所以循环(线程)不会退出
但是如果线程里面没有令线程产生阻塞的方法,比如sleep方法这种,这时interrupt就会直接修改内置标志位,当你代码执行完回到while进行判断时,由于内置位已经修改,所以退出循环(线程)
法三:使用 thread 对象的 interrupted() 方法通知线程结束
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public 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 ()
+ ": 有内鬼,终止交易! ");
// 注意此处的 break
break ;
}
}
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 ()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子! ");
thread . interrupt ();
}
}
thread 收到通知的方式有两种:
- 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志,当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程.
- 否则,只是内部的一个中断标志被设置, thread 可以通过,Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志,Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
interrupted跟isInterrupted的区别是:interrupted标志会自动清除,也就是说内置标志位本来是false,外面调用了interrupt,就会从false变成true,但读取到while(!Thread.interrupted()),会读到这个true,但是读完之后,这个标志位就自动恢复成false了
Java这里的线程终止,设定的是比较绕的,把决定权给要被结束的线程自身
操作系统原生的线程库,中断的时候,决定权是调用者
3.线程等待join
线程之间的执行顺序是完全随机的,看系统的调度
我们不能确定两个线程的开始执行顺序,但是可以通过join来控制两个线程的结束
public class Demo11 {
private static Thread t1 = null;
private static Thread t2 = null;
public static void main(String[] args) throws InterruptedException {
System.out.println("main begin");
t1=new Thread(()->{
System.out.println("t1 begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
});
t1.start();
t1.join();//让t1先执行完
t2=new Thread(()->{
System.out.println("t2 begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 end");
});
t2.start();
t2.join();//让t2执行完
System.out.println("main end");
}
}
运行结果:main begin
t1 begin
t1 end
t2 begin
t2 end
main end
这个代码,主线程先执行t1,然后join等待t1结束,t1结束之后,主线程在启动t2,再等待t2结束
join的行为:
- 如果被等待的线程还没执行完,就阻塞等待
- 如果被等待的线程已经执行完了,就直接返回
比如上面的代码,开启线程t1后,t1调用join,t1就是被等待线程,其他线程要等待t1执行完,如果开启t2,t2调用join,main等待t2结束
方法 | 说明 |
---|---|
public void join() | 等待线程结束(死等) |
public void join(long mills) | 等待线程结束,最多等mills毫秒 |
public void join(long mills.int nanos) | 同理但可以更高精度 |
第三个是join(100,1000)相当于等待100.001ms
4.线程休眠sleep
操作系统内核中管理PCB的链表实际上是有多个的
问:写了个sleep(1000)就一定会在1000ms之后就上CPU运行吗
答:不一定,1000ms之后只是把这个PCB放回了就绪队列,至于说这个线程啥时候能上CPU执行,还的看调度器的心情
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程millis毫秒 |
public static void sleep(long millis,int nanos) throws InterruptedException | 可以更高精度的休眠 |
5.获取当前线程引用
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
Thread.currentThread()//哪个线程调用,就返回哪个线程的对象