1.线程创建
Java创建线程时,代码有5种编写方式,如下:
(1) 继承Thread类,重写run()方法
代码:
运行结果:
(2)实现Runnable接口,重写run方法
代码:
运行结果:
这里值得一提的是使用Runnable接口,runnable对象是一个线程能够执行的任务,相对于(1)中所提到的方法,这种编写方式使得线程和任务分离,降低了线程和任务的耦合性。
(3)继承Thread类,使用匿名内部类,重写run方法
代码:
运行结果:
(4)实现Runnable接口,使用匿名内部类,重写run方法
代码:
运行结果:
可以看到对比(1)和(2),使用(3)和(4)两种方法,少写一个类,让代码从结构上更简单了。
(5)使用Lambda表达式
代码:
运行结果:
使用Lambda表达式有进一步简化了代码,这是最为推荐的方式。
以上是创建线程代码的5种写法,实际上线程的创建都是由start()方法完成的。调用start()方法,Java虚拟机调用系统API,在系统内核创建线程。
这里再说一下start()方法和run方法的区别:
- run()描述了线程要执行的任务,是线程的入口;
- start()会调用系统api,在系统内核中创建线程。Java虚拟机中,start会根据不同的系统,分别调用不同系统的api;
实际的执行过程是先调用start()创建一个线程,再自动调用run()执行线程的具体任务;
2.线程中断
Thread类中有一个boolean类型的成员变量interrupted,初始情况下,这个变量的默认值为false,一旦外部有线程调用该线程的interrupt()方法,该变量的值就会变成true,进而isInterrupt()方法会返回true。最常用的方式就是通过在其它线程调用interrupt()方法,实现线程的终止。如下图:
t线程中,除了打印信息,就是线程休眠,我们调用interrupt()方法,大概率线程还在睡眠,这时候interrupt()会唤醒线程,并抛出一个异常。
运行结果如下:
虽然抛出了异常,但是线程并没有被终止,而是继续运行,看起来像是isInterrupt()没有返回true,实际上使用interrupt()方法后,线程被唤醒,就会重置变量interrupt为false,导致isInterrupt()返回false;Java这样设计的目的是为了在抛出异常后,程序执行进入catch,程序员自己做一些操作,避免直接暴力终止线程,形成一些无用的中间产物,比如写了一半的文件。操作完成后再通过break/return来终止线程。如下:
运行结果:
总结:interrupt()方法能够设置标志位interrupt,能够唤醒sleep()等阻塞方法;但sleep()被唤醒之后,又会清除标志位interrupt;
3.线程等待
线程等待的目的是确定线程结束的顺序,但操作系统对线程的执行是“随机调度,抢占式执行”。这时候要让需要先结束的线程阻塞,等待需要后结束的线程,来保证线程结束的顺序,通常使用join()方法来实现,如下:
运行结果:
假如t线程先结束,那主线程也不用等待了,就更好了。如果t线程比主线程先结束,使用上面的方法也能确保t线程先结束。
上面的t.join()表示让主线程一直等待,直到t线程运行结束,如果t线程死循环了,那就不能一直等待了,所以join()可以带一个时间参数,单位是ms,如下:
运行结果:
上述代码,t线程进入死循环了,需要让主线程先结束,就使用一个时间参数,等待超时后让主线程先结束。
4.线程休眠
通常调用Thread.sleep()使线程进入休眠状态。通过调用interrupt()方法会唤醒线程,并抛出异常,如果不使用break/return结束线程,线程就会继续执行。如下:
5.获取线程实例
currentThread是Thread类的静态方法。在某个线程内部调用该方法,会返回当前线程,可以参考Java的源码: