目录
创建线程的五种方法
Java中创建线程有五种方法。
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) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread();
t.start();
while (true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
注:run方法描述了线程要执行什么任务,start方法表示创建线程,线程就会自动的把run进行执行。
2)实现Runnable接口,重写run,把Runnable实例传入到Thread中。
class MyRunnable implements Runnable{
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
注:Runnable的作用,是描述了一个“任务”,这个任务和具体的执行机制无关,(通过线程的方式执行,还是通过其他的方式执行),run也就是要执行的任务内容本身了。
第一种写法是Thread自己记录我要干啥,自己记下来自己的作业。
第二种写法,通过Runnable记录作业是啥。Thread负责执行,别人给你把作业记下来了,然后Thread负责执行。(将两个功能剥离开,可以起到解耦合的作用)
3、4)本质上是一个写法,只是使用匿名内部类实现
public class ThreadDemo3 {
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) {
throw new RuntimeException(e);
}
}
}
};
t.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo4 {
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) {
throw new RuntimeException(e);
}
}
}
});
t.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
注:这里创建了Thread的子类,子类内部重写了父类的run方法,同时创建了该子类的实例,对于匿名内部类来说,只能创建一个实例,这个实例创建完了之后,再也拿不到这个匿名内部类了。
正常的编程语言一般是使用“匿名函数”的机制来完成的,但是在Java中函数不能独立存在,必须要包裹上一个类,于是引入了匿名内部类。而匿名内部类的写法很别扭,于是就引入了lambda表达式。
5)使用lambda表达式,就是对匿名内部类的代替。本质上就是一个“匿名函数”,一次性的函数,用完就丢。
public class ThreadDemo5 {
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.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
上述五种写法,本质上都是1)要把线程执行的任务内容表示出来。2)通过Thread的start来创建/启动系统中的线程。(Thread对象和操作系统内核中的线程是一一对应的关系)
线程等待和阻塞
线程终止
终止线程,在Java中,都只是“提醒,建议”,真正要不要终止,还得线程本体来进行决定的。系统原生的线程中,其实是有办法让别的线程被强制终止的。这种设定,其实不太好,所以java没有采纳过来。
Thread类提供了interrupt方法和isInterruptted方法,来实现上述的效果。
currentThread()是个static方法,能够获取到当前线程,能获取到t这个引用。
isInterrupted()查看一个boolean变量,是线程内置的标志位,true表示线程终止,false表示线程继续执行。
interrupt方法相当于直接设置标志位为true.
线程等待
多个线程,调度顺序,在系统中是无序的(抢占式执行)。多个线程啥时候被调度执行,不确定。我们不想要这种效果。那么通过线程等待,就能够确定线程结束的先后顺序。
Thread类中提供join方法令线程等待(进入阻塞状态)。
阻塞:该线程暂时不参与cpu调度执行,解除阻塞继续执行,线程重新参与到cpu调度了。
main线程中调用t.join()就是让main等待t,也就是t先结束,main后结束。
join()除了无参数版本,还有带参数版本
传入的参数即是等待的最大时间,如果时间到了t还没结束,就继续往下走。
线程休眠
sleep()方法控制线程休眠,及强制令线程进入阻塞状态一段时间。注意此处设置sleep的时间是线程阻塞的时间。设定时间之内,线程一定不会去cpu上执行(阻塞),当时间到了之后,线程从阻塞状态恢复到就绪状态,不代表就能立即去cpu上执行。从恢复就绪到真正去cpu上执行,还是需要一定时间的(具体是多少,就看机器配置,系统繁忙程度了)。
Thread中的核心属性和方法
Thread还提供了关于线程的其他属性和方法。
优先级:虽然java提供了优先级接口,实际上就算修改了优先级,现象也不明显。修改是一回事,系统调度又是另外一回事,这里的优先级只能是一个“建议参考”,具体还是人家系统以自身为准。
是否前台线程:
前台线程:这样的线程不运行结束的话,此时java进程是一定不会结束的。
后台线程:这样的线程,即使继续在执行,也不能阻止java进程结束。
前台线程可以有多个,多个前台线程必须得是最后一个前台线程结束,整个进程才结束。
在java代码中,main线程就是前台线程,程序员创建出来的线程,默认都是前台线程,可以通过上述setDaemon方法来把线程设置为后台线程。
是否存活:判断系统中的线程pcb是否还存在。
Thread对象的生命周期,和pcb的生命周期是不一定完全一样的。
这个代码已经创建了Thread的实例,Thread对象已经诞生了,但是,此时内核中的pcb还没有诞生。t.start()之后,才是真正在系统中创建出线程(pcb才真正创建出来并且加入到链表中)。
由于t线程中的内容,啥都没写。所以t瞬间就结束了,内核中的线程和pcb就被销毁了。但是在sleep结束之前,t引用指向的Thread对象,仍然是存在的,并没有被GC(垃圾回收机制)回收掉。此时就看到,系统中的线程先结束了,但是t仍然还在。
这个写法,就会导致线程还没执行完毕,但是t指向的对象就要被GC回收了。所以Thread对象的生命周期和系统中的线程的生命周期是不一致的。就可以通过上述的isAlive方法,判定系统中的线程是否依然存在。
以上,关于Thread类的介绍,希望对你有所帮助。