简介
什么是线程
进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程:一个线程不能独立的存在,它必须是进程的一部分。
线程的生命周期
生命周期实现方式
大家可能在面试过程中、文章中或者书上看到过“实现线程有几种方式?”这样的问题,基本都是千篇一律的说两种方式:1、继承Thread重写run方法;2、实现Runnable接口。其实这里是有问题的,总所周知在Java中使用Thread表示线程的,所以实现Thread只有一种方式:那就是构造Thread类。而实现线程的执行单元有两种方式:1、继承Thread重写run方法;2、实现Runnable接口的run方法,并将Runnable实例作为构造Thread的参数。
API讲解
构造函数
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...
}
强烈建议:在构造线程的时候为线程取一个有特殊意义的名字,有助于问题的排查和线程的跟踪(特别是在线程比较多的程序中)。
Thread.sleep和TimeUnit
sleep()方法导致程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
sleep是一个静态方法,有两个重载方法,其中一个需要传入毫秒数,另一个既需要毫秒数也需要纳秒数。
public static void sleep(long millis)
public static void sleep(long millis, int nanos)
TimeUnit是JDK1.5以后引入的,其对sleep有更好的封装性,能更好地、更精准地控制。
TimeUnit.HOURS.sleep(1);
TimeUnit.MUNUTES.sleep(1);
TimeUnit.SECONDS.sleep(1);
强烈建议:用TimeUnit代替Thread.sleep,因为sleep能做的事,TimeUnit都能做并且功能更强大。
Thread.yield
yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前CPU资源。如果CPU资源不紧张,则会忽略这种提醒。如果生效,会使当前线程从Running状态切换到Runnable状态。
new Thread(() -> {
Thread.yield();
System.out.println("yield");
}
).start()
yield和sleep区别
- sleep会导致线程暂停指定时间,会使线程处于block状态;yield如果CPU调度器没有忽略,会导致线程处于Runnable状态。
- sleep几乎百分之百地完成给定时间的休眠;yield并不能保证成功。
- 一个线程sleep,另一个线程调用interrupt会捕获到中断信号;yield不会。
join
join()方法是Thread类中的一个方法,该方法的定义是等待该线程终止。其实就是join()方法将挂起调用线程的执行,直到被调用的对象完成它的执行。join与sleep一样是一个可中断的方法。
ThreadTest test =new ThreadTest();
ThreadTest test2 =new ThreadTest();
test.setName("one");
test2.setName("two");
Thread t1 = new Thread(test);
Thread t2 = new Thread(test2);
t1.start();
/**
* 主线程向下转时,碰到了t1.join(),t1要申请加入到运行中来,就是要CPU执行权。
* 这时候CPU执行权在主线程手里,主线程就把CPU执行权给放开,陷入冻结状态。
* 活着的只有t1了,只有当t1拿着执行权把这些数据都打印完了,主线程才恢复到运行中来
*/
//join 方法 确保 t1执行之后 执行t2
t1.join();
t2.start();
join和CountDownLatch区别
- 都适用“主程序中需要等待所有子线程完成后 再继续任务”的场景
- 调用join方法需要等待thread执行完毕才能继续向下执行,而CountDownLatch只需要检查计数器的值为零就可以继续向下执行,相比之下,CountDownLatch更加灵活一些,可以实现一些更加复杂的业务场景。
interrupt、interrupted、isInterrupted
interrupt
interrupt()方法: 作用是中断线程。
- 本线程中断自身是被允许的,且"中断标记"设置为true
- 其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
- 若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。
例如:线程通过wait()、sleep()、join()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。 - 如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
interrupted和isInterrupted
- interrupted和isInterrupted都是判断当前线程是否被中断
- interrupted会清除掉线程的interrupt标志;isInterrupted不会
System.out.println("Main Thread is interrupted? " + Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("Main Thread is interrupted? " + Thread.currentThread().isInterrupted());
// System.out.println("Main Thread is interrupted? " + Thread.interrupted());
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.println("I will be interrupted still.");
}
思考:打开注释和关闭注释的区别?
如何关闭一个线程
- 正常关闭
1、 线程正常结束,线程自动退出
2、捕获中断信号
3、适用volatile开关控制 - 异常退出
补充:Thread API中包含了一个stop()方法,可以突然终止线程。但它在JDK1.2后便被淘汰了,因为它可能导致数据对象的崩溃。一个问题是,当线程终止时,很少有机会执行清理工作;另一个问题是,当在某个线程上调用stop()方法时,线程释放它当前持有的所有锁,持有这些锁必定有某种合适的理由——也许是阻止其他线程访问尚未处于一致性状态的数据,突然释放锁可能使某些对象中的数据处于不一致状态,而且不会出现数据可能崩溃的任何警告。
守护线程
若JVM中没有一个非守护线程时,JVM的进行会退出。
守护线程具备自动结束生命周期的特性,经常用于执行一些后台工作,因此有时它也被称为后台线程。
//伪代码如下
Thread thread = new Thread();
thread.setDaemon(true);
thread.start()
网友评论