Thread类是Java中进行多线程编程无法避开的一个类,通过这个类中的方法我们可以对线程进行许多操作,在本文中我们就来介绍一下Thread类的基本用法.
1. 线程创建
1.1 创建Thread类的实例对象----t
总体而言,利用Thread类创建线程有5种创建方式:
1.1.1 继承Thread类,重写run
class MyThread extends Thread {
@Override
public void run() {
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t = new MyThread();
}
}
1.1.2 实现Runnable,重写run
class MyThread2 implements Runnable {
@Override
public void run() {
}
}
public class Demo2 {
public static void main(String[] args) {
Thread t = new MyThread();
}
}
1.1.3 继承Thread,重写run,使用匿名内部类
public class Demo3 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
}
}
}
}
1.1.4 实现Runnable,重写run,使用匿名内部类
public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
}
});
}
}
1.1.5 使用lambda表达式
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(()->{
});
}
}
我们可以发现不论利用哪一种创建方式,都创建Thread类的一个实例对象--t,通过对这个t调用相应的方法就可以使得线程达到相应的效果,我们可以接着往下看.
1.2 线程底层真正创建----调用start方法
start方法源码的分析,在上一篇博客中已经进行了较为详细的分析,感兴趣的读者可以移步:
Thread类中start方法和run方法的源码简单解读,联系和区别-CSDN博客 查看.
总结来说,start方法做了两件事情:
1. 开启线程,包括调用传入的Runnable对象中被重写的run方法.
2. 开启成功或失败后的收尾操作.
因此,我们认为只有在描述线程的对象t上调用start方法,才算是真正地开启了一个线程.下面给出一个示例:
public class test {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开启线程");
}
});
t.start();
}
}
2. 线程中断
在一个线程执行过程中,并非是一直能够顺利地进行下去,而是有可能有时系统认为这个线程继续执行下去有危险,因此会中断当前这个线程.我们在进行多线程编程时,也可以根据需求来中断一个线程.
线程的中断通常有两种方式:
2.1 引入共享的标记
这里直接给出代码:
public class test {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread t = new Thread(target);
t.start();
Thread.sleep(10 * 1000);
target.isQuit = true;
}
}
2.2 调用interrupt()方法来通知
public class test {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!Thread.interrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread t = new Thread(target);
t.start();
Thread.sleep(10 * 1000);
Thread.interrupted();
}
}
thread收到通知的方式有两种:
1.如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通 知,清除中断标志.当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法.可以选择忽略这个异常,也可以跳出循环结束线程.
2.否则,只是内部的一个中断标志被设置,thread可以通过.Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志这种方式通知收到的更及时,即使线程正在sleep也可以马上收到。
3. 线程等待
在一个线程执行过程中,并非是一直能够顺利地进行下去,而是有可能需要另外一个线程先完成某个任务才能继续向下执行,那么这个线程就要进入等待状态.我们在进行多线程编程时,也可以根据需求来使一个线程等待.
join方法的调用
我们这里也直接给出代码示例:
public class Demo {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("hello thread");
});
t.start();
t.join();
System.out.println("hello main");
}
}
这里提出一个问题:如果删去上述代码中的t.join()语句,那么打印出的结果是什么呢?这里应该先打印出"hello main"再打印出"hello thread".因为,t线程的创建需要时间,在这段时间内main线程就会打印出"hello main".
但是,当我们加上t.join()语句,就相当于main线程调用了join这个方法,进入等待阻塞状态,只有当线程t执行完之后,才会继续执行main线程.
join方法的出现使得我们可以控制线程的执行顺序,但是也一定程度上降低了并发性,有点类似于之前数据库事务中所提及的"串行化".
在Thread类中,join方法中共有3个方法重载.
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等millis毫秒
public void join(long millis,int nanos) 同理,但可以达到更高精度
第一种是我们示例中使用的,被称为死等.它所等的线程如果不结束,它永远不会继续执行.
后两种就比较明智----等有限的时间,等不到就不等了!!!不容易发生卡死的情况,使得程序更灵活.
4. 线程休眠
线程的休眠和线程的等待十分相似,都可以通过设置时间让线程暂且停止执行,但是不同之处在于线程等待与其他线程有关,但线程的休眠仅仅只与这个线程本身有关.
sleep方法的调用
我们也直接给出代码示例:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(3 * 1000);
System.out.println(System.currentTimeMillis());
}
}
但是因为线程的调度是不可控的,所以这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的.我们可以调用currentTimeMillis方法获取当前系统时间戳,等sleep后,再获取时间戳,相减即可得到实际上真正等待的时间.
5. 获取线程对象引用
这个也十分简单,只需要调用Thread类中的currentThread方法即可,直接给出示例:
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}