一、线程创建
1.1 Thread的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
2.1 创建线程
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
二、线程中断
2.1 Thread的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行
- 是否存活,即简单的理解,为run方法是否运行结束了
2.2 中断线程
中断线程,就是让一个线程停下来,本质上说,让一个线程终止,只有一个办法,那就是让线程的入口方法,执行完毕!
目前常见的有以下两种方式用以中断线程:
- 通过共享的标记来进行沟通
- 调用interrupt()方法来通知
第一种:需要自定义一个标志位,并且需要给该标志位加上一个volatile关键字修饰,代码如下:
package threrading;
public class ThreadDemo8 {
//lambda表达式中访问外部的局部变量遵循 变量捕获语法规则
//java中要求变量捕获必须是finall或者没有被修改过的变量
public volatile boolean isQuit = false; //设置标志位 控制循环
public static void main(String[] args) {
Thread t = new Thread( ()->{
while (!isQuit){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t 线程结束");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在主线程中修改isQuit
isQuit=true;
}
}
第二种: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位,代码如下:
package threrading;
public class ThreadDemo9 {
public static void main(String[] args) {
Thread t = new Thread(()->{
//currentThread是获取当前线程的实例
//此处currentThread 得到的就是t
//isInterrupted 就是t对象里自带的一个标志位
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//报异常之后,如果需要结束循环,就需要在这下面手动加一个break;
e.printStackTrace();
break;
}
}
});
t.start(); //代码执行到这里,主线程继续执行,新线程进入run中去执行,两个进程并发执行
/**
* 如果slepp执行时看到这个标志位是false,sleep正常进行休眠操作
* 如果当前标志位是true,
* sleep无论是刚刚执行还是已经执行了一半,都会触发两件事:
* 1.立即抛出异常
* 2.清空标志位为false (此后,标志位一直为false,因为主线程的t.interrupt();只被执行一次)
*/
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把t内部的标志位 设置为true
// 主线程并非是循环反复设置,而是只执行一次
t.interrupt(); //这个执行完,主线程就执行完了
/*
interrupt的作用:
1.设置标志位为true
2.如果该线程正在阻塞中(比如在执行sleep)
此时就会把阻塞状态唤醒
通过抛出异常的方式让sleep立即结束
*/
/*
注意:
当sleep被唤醒的时候,sleep会自动把isInterrupted标志位自动清空(true-->false)
为啥要sleep清空标志位呢?
目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制
*/
}
}
三、线程等待
线程之间是并发执行的,操作系统对于系统的调度,是无序的,无法判定两个线程谁先执行结束,谁后执行结束!但是在实际开发过程中,有时候我们需要明确规定某一个线程要先执行完,这时候我们就需要调用线程的 join() 方法。
假设在一段代码中有一个线程 t 以及其主线程(main), t 线程启动之后,如果在main线程中调用 t.join() ,意思就是让main线程等待 t 线程先结束,再往下执行!!!
如果是 t1 线程中调用 t2.join() ,就是让 t1 线程等待 t2 线程先结束,此时 t1线程进入阻塞状态,其他线程正常调度。
package threrading;
public class ThreadDemo10 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
System.out.println("hello t");
});
t.start();
t.join(); //在执行时,如果t线程还没有结束,main线程就会 阻塞(Blocking) 等待。如果t已经结束,就不会阻塞,main继续执行。
//是在main线程中,调用t.join。意思是让main线程等待t先结束,再往下执行
//如果是t1线程中,调用t2.join。就是让t1线程等待t2先结束
//t1线程进入阻塞,其他线程正常调度
System.out.println("hello main");
}
}
四、线程休眠
休眠线程,就是调用Thread.sleep()方法,这里会抛出异常。
但是值得注意的是,由于线程的调度是不可控的,所以这个方法只能保证实际休眠时间大于等于参数设置的休眠时间。
五、获取线程实例
获取线程的实例非常的简单,就是调用Thread.currentThread()方法,这个在线程中断部分提及过,下面是简单的代码示例:
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}