1.线程的常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】Thread(ThreadGroup group, Runnable target) |
线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可 |
2.线程的几个属性和方法
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
这里的后台线程和前台线程不同,当所有的前台线程执行完毕,即使后台线程还在工作中,也会直接自动退出.
只要前台线程没执行完,进程就不会结束,即使main结束了,前台线程也会继续执行
设置为后台进程
setDaemon()设为true就是后台,不设置就是默认前台
isAlive()表示内核中的PCB是否存在
这个对象的生命周期和PCB的是不完全一样的
因为我在创建对象之后这个PCB才存在,在执行完进程结束后之后这个PCB才销毁,而且java中的用户级线程不是直接映射到操作系统中的原生线程的
3.创建线程的5个方式
其实线程是操作系统提供的一种机制,并给用户提供了一些api供使用,Java的Thread类只是对其的进一步封装.
1.继承Thread类,重写run方法
2.重写runnable接口
3.使用匿名内部类实现Thread
4.使用匿名内部类实现Runnable接口
5.使用Lambda表达式
4.start()和run()方法的区别
说实话这两个方法是八竿子打不着的,为什么要把他们两个放进来比较呢?
可能有人认为start方法和run方法执行的是一件事情
其实这个理解是大错特错的,这两个方法完全不一样,我们举个例子
假设我们在调用main方法中调用一个run方法,你可能以为这样和使用start方法是一样的,其实不然,我们在run方法中是一个死循环,在main方法中调用run方法,下面再写一个死循环,此时控制台只会打印线程中的死循环的内容而不会打印main方法中的内容
而start方法实际上创建了一个新的线程,根据cpu的随机调度(抢占式调度),两者都可能打印出来,下面我给出代码支持.
package Test; import java.awt.desktop.ScreenSleepEvent; public class ThreadDemo2 { public static void main(String[] args) { Thread t = new Thread(()->{ while (true){ System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.run(); while (true){ System.out.println("Hello main"); } } }
package Test; import java.awt.desktop.ScreenSleepEvent; public class ThreadDemo2 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ while (true){ System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); while (true){ System.out.println("Hello main"); Thread.sleep(1000); } } }
5.终止一个线程
package Thread; public class ThreadDemo12 { private static boolean isQuit = false; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ while(!isQuit){ System.out.println("我是一个线程,工作中"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("线程工作完毕"); }); t.start(); Thread.sleep(3000); isQuit = true; System.out.println("让t线程退出"); } }
最后结果为
这就好比这个时候我在打游戏,女朋友突然叫我去和她一起开心一下,这个时候就是main线程来中断我这个t线程,我得配合她她才能中断我的动作,假设我不配合,那她也没有办法.
所以这里的线程运行中想要中断,其实是需要两个线程中相互配合的,这里就使用了isQuit变量来实现其中的相互配合.
.
其实这里也有一个方法来实现对线程的中断,我们来尝试一下
interrupt()
isInterrupted() 判断是否被打断
我们不妨来试试用这个来作为标志位
package Thread; public class ThreadDemo13 { public static void main(String[] args) { Thread t = new Thread(()->{ while(!Thread.currentThread().isInterrupted()) { System.out.println("我是一个线程,正在工作"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程执行完毕"); }); t.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("t线程退出"); t.interrupt(); } }
结果是
我们发现t线程并没有真正的执行结束,并且捕获了一个打断异常
结论:
sleep中的线程被打断之后,线程并没有直接退出,而是继续一直输出信息
这是因为线程在睡眠中被打断会抛出一个异常,并将isInterrupted重新设置为false
解决方案,在收到这个打断的异常之后,直接break跳出循环
有人会觉得还不如之前的isQuit标志位好,想在遇到这个异常信息的时候将isQuit设置为true,其实是行不通的,可以参考我上一篇文章中的变量捕获.因为匿名内部类中调用的局部变量只能是final修饰的或是事实final的
6.线程中的join方法
join方法能够实现线程的等待,假如在main线程中调用t.join,此时main线程就会等待t线程执行结束后再继续工作,t线程执行的时候,此线程属于阻塞状态
下面举个例子说明一下
package Thread; public class ThreadDemo14 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ for (int i = 0; i < 5; i++) { System.out.println("马上到..."); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println("终于到了!!!"); }); t1.start(); t1.join(); System.out.println("我等你好久了~~~"); } }
执行结果如下
注:start方法一定在join方法之前调用
此时main线程等待了t1线程执行完了才开始执行