Java学习总结之多线程(2)
1.Thread类的及常见方法
1.1Thread类的构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象并创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name)) | 使用Runnable对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
1.2Thread类的几个属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否为后台线程 | idDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,就是run 方法是否运行结束了
2.线程的常见方法
2.1启动一个线程-start()
- 调用start()方法,才是真正在操作系统底层创建出一个线程
- 重写run()方法创建了一个线程对象,但线程对象创建出来并不意味着线程开始运行了
- 只有调用了start()方法,线程才真正开始运行
//创建线程对象,线程还没有开始运行
Thread thread = new Thread(()->{
//业务代码
Thread thread3 = Thread.currentThread();
System.out.println("名称" + thread3.getName());
});
//启动线程,线程运行
thread.start();
2.2中断一个线程的两种方式
2.2.1通过共享标记来进行沟通
/**
* 自定义标识符终止线程
*/
public class InterruptThread1 {
//声明一个自定义标识符
private volatile static boolean flag = false;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(!flag){
System.out.println("正在转账");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("差点误了大事");
});
thread.start();
Thread.sleep(10000);
InterruptThread1.flag = true; //改变共享标记,中断线程运行
System.out.println("执行结束");
}
}
2.2.2调用interrupt()方法来通知
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程在阻塞,则以异常的方式进行通知,否则设置标记位 |
public statci boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
- 使用thread对象的interrupted()方法通知线程结束
public class InterruptThread1 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(!Thread.interrupted()){ //或者!Thread.currentThread().isInterrupted()
System.out.println("正在转账");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("差点误了大事");
});
thread.start();
Thread.sleep(10000);
thread.interrupt(); //通知线程结束
System.out.println("执行结束");
}
}
thread收到通知的方式有两种
- 如果线程因为调用wait/join/sleep等方法引起阻塞而挂起,则以InterruptedException异常的方式进行通知,清除中断标志
- 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择
忽略这个异常, 也可以跳出循环结束线程.
- 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择
- 否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
2.3等待一个线程-join()
有时候,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等millis毫秒 |
public void join(long millis,) | 更高精度 |
2.4获取当前线程引用
方法 | 说明 |
---|---|
public static Thread currentThread() | 返回当前线程对象的引用 |
2.5休眠当前线程
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
- 因为线程调度是不可控的,因此这个方法只能保证实际休眠时间大于等于参数设置的时间
3.线程的状态
状态 | 说明 |
---|---|
New | 新创建 |
Runnable | 可运行 |
Blcoked | 被阻塞 |
Waiting | 等待 |
Timed waiting | 计时等待 |
Terminated | 被终止 |
3.1新创建线程
当用 new 操作符创建一个新线程时, 如 newThread®, 该线程还没有开始运行。这意味着它的状态是 new。当一个线程处于新创建状态时, 程序还没有开始运行线程中的代码。在线程运行之前还有一些基础工作要做。
3.2可运行线程
- 调用线程的start()方法,此线程进入可运行状态。
- 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。
- 当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入可运行状态。
- 锁池里的线程拿到对象锁后,进入可运行状态。
3.3被阻塞线程和等待线程
被阻塞线程
阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu 时间片,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu 时间片转到运行(running)状态
- 当前线程T调用Thread.sleep()方法,当前线程进入阻塞状态。
- 运行在当前线程里的其它线程t2调用join()方法,当前线程进入阻塞状态。
- 等待用户输入的时候,当前线程进入阻塞状态
- BLOCKED 表示等待获取锁, WAITING 和 TIMED_WAITING 表示等待其他线程发来通知.
TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒
等待线程
- 当线程等待另一个线程通知调度器一个条件时, 它自己进入等待状态 在调用 Object.wait 方法或 Thread.join 方法, 或者是等待 java.util.concurrent 库中的 Lock 或 Condition 时, 就会出现这种情况。实际上,被阻塞状态与等待状态是有很大不同的
- 有几个方法有一个超时参数。调用它们导致线程进人计时等待( timed waiting) 状态。这一状态将一直保持到超时期满或者接收到适当的通知。带有超时参数的方法有Thread.sleep 和 Object.wait、 Thread.join、 Lock,tryLock 以及 Condition.await 的计时版。
结论
- BLOCKED 表示等待获取锁, WAITING 和 TIMED_WAITING 表示等待其他线程发来通知.
- TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒
3.5被终止线程
- 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
- 在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
4.线程状态的转移
4.1关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换
使用 isAlive 方法判定线程的存活状态
public class ThreadStateTransfer {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 1000_0000; i++) {
}
}, "李四");
System.out.println(t.getName() + ": " + t.getState());;
t.start();
while (t.isAlive()) {
System.out.println(t.getName() + ": " + t.getState());;
}
System.out.println(t.getName() + ": " + t.getState());;
}
}
4.2yield() 大公无私,让出 CPU
public class ThreadYield {
public static void main(String[] args) {
Thread thread = new Thread(() ->{
Thread t1 = Thread.currentThread();
for (int i = 0; i < 10; i++) {
//让出CPU执行权
Thread.yield();
System.out.println("执行了线程" + t1.getName());
}
},"张三");
thread.start();
//创建并启动线程
new Thread(() -> {
Thread t1 = Thread.currentThread();
for (int i = 0; i < 10; i++) {
System.out.println("执行了线程" + t1.getName());
}
},"李四").start();
}
- yield 不改变线程的状态, 但是会重新去排队
5.线程的生命周期
- 新建:创建线程对象
- 就绪:线程有执行资格,没有执行权
- 运行:有执行资格,有执行权
- 阻塞:由于一些操作让线程改变了状态,没有执行资格,没有执行权,另一些操作可以把它给激活,激活处于就绪状态
- 死亡:线程对象变成垃圾,等待被回收