文章目录
二. Thread类及常见方法
2.1 常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】Thread( ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
//
}
});
Thread t4 = new Thread("这是我的名字");
Thread t5 = new Thread(new MyRunnable(), "这是我的名字");
Thread t6 = new Thread(new Runnable() {
@Override
public void run() {
//
}
}, "这是我的名字");
2.2 Thread 的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID是线程的身份标识(JVM给线程设定的标识)
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 后台线程也称为守护线程, 不影响进程的结束
- 前台线程, 会影响到进程的结束, 如果前台线程没执行完进程, 是不会结束的.
- 一个进程中所有的前台线程都执行完并退出了, 即使后台线程没有执行完, 也会跟着进程一起推出.
- 是否存活,即简单的理解,为 run 方法是否运行结束了
2.3 启动一个线程 start()
调用start()方法才能在操作系统中创建出一个线程.
调用完start方法后, 代码会立即继续执行start后续的逻辑.
2.4 终止一个线程
当一个线程的run方法执行完毕, 线程就算终止了. 此处的终止线程, 就是让run能够尽快的执行完毕.
如何使其尽快结束?
-
手动设定标志位.
public class Demo { public static boolean isQuit = false;//标志位 public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (!isQuit) { System.out.println("Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); //主线程这里执行一些其他逻辑之后, 让t线程结束 Thread.sleep(3000); isQuit = true;//修改标志位 System.out.println("t线程终止"); } }
如果我们把代码改成这样
public class Demo { public static void main(String[] args) throws InterruptedException { boolean isQuit = false;//标志位 Thread t = new Thread(() -> { while (!isQuit) { System.out.println("Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); //主线程这里执行一些其他逻辑之后, 让t线程结束 Thread.sleep(3000); isQuit = true;//修改标志位 System.out.println("t线程终止"); } }
编译器会报错, 为什么?
因为我们创建线程借用的是lambda表达式, 他是一个回调函数, 只有时机到了才会执行, 所以他的执行时间是靠后的, 这就导致后续真正执行lambda的时候, 局部变量isQuit可能已经被摧毁了.
所以让lambda去访问一个已经被销毁的变量是不合适的.
lambda便引入了"变量捕获"这样的机制 : lambda内部看起来是在直接访问外部的变量, 其实本质上是吧外部的变量给复制一份到lambda里面, 这样就解决生命周期的问题了.
但是, 变量捕获有个限制, 要求捕获的变量final的, 即不可变的. 而上述代码在主线程内修改了被捕获变量的值, 所以报错了.
我们的第一种写法没有触发变量捕获, 而是内部类访问外部类的成员, 这种写法是合理的.
-
使用
Thread.interrupted()
或者Thread.currentThread().isInterrupted()
代替自定义标志位.
public class Demo { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { System.out.println("Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); Thread.sleep(3000); t.interrupt(); System.out.println("0"); } }
-
Thread.currentThread()
: 获取到当前的Thread对象. 在代码中就相当于t, 为什么不直接用t呢? 因为lambda表达式是在构造t之前就定义好的, 如果直接用t就会使编译器认为他是一个还没初始化的对象. -
isInterrupted()
: Thread对象内部提供了一个标志位(boolean), 若方法返回true, 则表示线程应该结束, 若是false, 线程先不必结束. -
interrupt()
: 把标志为设置成true. -
如果运行上述代码, 会爆出一个异常, 并且循环不会终止
这个异常的意思是, 睡眠时被唤醒. 在被唤醒后, 标志位会被自动清除, 如果还想让线程停止, 直接在catch里面加个break即可.
-
当sleep被唤醒后, 可以有以下几种操作方式
- 立即结束线程: 在catch中直接break.
- 继续做其他事情, 一会在结束线程: 在catch中执行别的逻辑, 执行完了再break.
- 忽略终止请求, 继续执行该线程: 不写break.
-
2.5 等待一个线程 join()
有时, 我们需要等待一个线程完成它的工作后, 才能进行自己的下一步工作. 例如, 张三只有等李四转账成功, 才决定是否存钱, 这时我们需要一个方法明确等待线程的结束.
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
a, b两个线程, 希望b先结束, a后结束, 就可以在a线程中调用b.join方法. 如果b县城还没执行完, a线程会进入阻塞状态. b执行完后, a再从阻塞状态中恢复回来, 继续往后执行.
public class Demo {
public static void main(String[] args) {
Thread b = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("b");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("b end");
});
Thread a = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("a");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a end");
});
a.start();
b.start();
}
}
2.6 获取当前线程的引用
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
2.7 休眠当前线程
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |