对于面试中的多线程问题中,Thread是很重要的一个类,下面我们就看看Thread类中到底有什么奇奇怪怪的东西以及如何回答面试官的各种刁难人的问题。
Thread使用
线程名
在使用多线程的时候,常用到的方法就是设置和查看现场名即setName(String name)和getName()。
默认情况下主线程的名字是main,其他线程名是Thread-x,x代表第几个线程。究其原因还要看其构造方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
我们可以看到其调用了init方法,init方法内部调用了nextThreadNum方法,这个方法是一个线程安全的方法,同一时间内只可能有一个线程修改。而threadInitNumber是一个静态变量,它可以被类中所有的对象访问,所以,每个线程在创建的时候直接+1作为子线程后缀。
线程优先级
线程的优先级可以理解为线程抢占CPU时间片的概率,优先级越高,获取的几率越大,但是不意味着,优先级高的就一定先执行。
优先级默认为5,其最大值为10最少值为1。
那么如何保证线程执行的先后顺序呢?
- 可以使用join()方法
Thread.join()把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
守护线程
守护线程看名字就知道很卑微,守护线程是低优先级的线程,专门为其他线程服务的,其他线程执行完毕了,会随着JVM一起退出,垃圾回收线程就是一个典型的守护线程。
主要特点
- 当别的非守护线程执行完毕了,虚拟机就会退出,守护线程也就会被停止
- 守护线程作为一个服务线程,没有服务对象就没有必要继续运行了
- 守护线程不要访问共享资源,因为它可能随时挂掉
- 守护线程中产生的新线程也是守护线程
可以通过setDaemon来设置守护线程。
public final void setDaemon(boolean on) {
// 判断是否有权限
checkAccess();
// 判断是否活跃
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
通过源码可以得知,必须在线程启动之前就把目标线程设置为守护线程,否则报错。
start()和run()的区别
算是面试常考问题,也十分容易混淆
- run方法里面定义的是线程执行的任务逻辑,直接调用跟普通方法没啥区别
- start 方法启动线程,使线程由 NEW 状态转为 RUNNABLE,然后再由 jvm 去调用该线程的 run () 方法去执行任务
- start 方法不能被多次调用,否则会抛出 java.lang.IllegalStateException;而 run () 方法可以进行多次调用,因为它是个普通方法
JVM执行start方法,会另起一条线程执行thread中的run方法,这才能起到多线程的作用,如果直接调用thread中的run方法,那么还是在主线程中,没有起到多线程的效果。
sleep方法
- 睡眠指定毫秒数,再次期间不是释放锁
- 如果参数非法,抛出异常IllegalArgumentException
- 睡眠状态下可以相应中断信号,并抛出InterruptedException
如何正确停止线程
首先要明确的是不要使用stop()方法,太暴力了,没有给线程足够的时间来处理在线程停止前保存数据的逻辑,任务就停止了,会导致数据完整性的问题。
一般情况下使用interrupt方法来请求停止线程,它相比stop就温和的多,仅仅是给线程发送了一个信号告诉他,应该要结束了,然后由线程根据自己的业务逻辑进行决定如何停止以及是否停止。
interrupt方法中有四个点比较关键
- 只能自己中断自己,不然会抛出SecurityException
- 如果线程调用wait、sleep、join等方法进入了阻塞,会造成调用中断无效,抛出InterruptedException异常。
- 以上情况不会发生时,才会把线程的中断状态改变。
- 中断已经挂了的线程是无效的。
另外,java 中跟中断有关的方法还有 interrupted() 和 isInterrupted()
- isInterrupted () 用于判断中断标志位,调用不会影响当前标志位
- interrupted () 用于清除中断标志位,调用会清除标志位
yield方法
看Thread的源码可以知道yield()方法为本地方法。由C或C++实现。具有不稳定性。
当前线程调用yield()会让出CPU使用权,给别的线程执行,但是不确保真正让出,谁先抢到CPU谁先执行
当前线程调用yield()方法,会将状态从RUNNABLE转为WAITING
join方法
调用join方法,会等待该线程执行完成后才会执行别的线程,用这个方法可以实现线程的有序执行。
其底层还是通过wait()方法实现的
当前线程终止,会调用当前实例的notifyAll方法唤醒其他线程
调用join方法,会使当前线程从RUNNABLE状态转为WAITING状态。
Thread实战演练
给线程起名字
- 实现了Runnable的方式来实现多线程
public class MyRunnable implements Runnable {
@Override
public void run() {
//打印出当前线程的名字
System.out.println(Thread.currentThread().getName());
}
}
- 测试
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// 使用带参构造方法给线程起名字
Thread t1 = new Thread(my,"关注我");
Thread t2 = new Thread(my,"冢狐");
t1.start();
t2.start();
// 打印出当前线程的名字
System.out.println(Thread.currentThread().getName());
}
}
- 结果
守护线程
- 设置守护线程
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// 使用带参构造方法给线程起名字
Thread t1 = new Thread(my,"关注我");
Thread t2 = new Thread(my,"冢狐");
//设置守护线程
t2.setDaemon(true);
t1.start();
t2.start();
// 打印出当前线程的名字
System.out.println(Thread.currentThread().getName());
}
}
- 结果
线程1和主线程执行完了,我们的守护线程就不执行了,所以要在启动前设置守护线程
线程停止
public class Main {
public static void main(String[] args) {
Main main=new Main();
//创建线程并启动
Thread t=new Thread(main.runnable);
System.out.println("This is main");
t.start();
try {
// 在main线程睡个三秒钟
Thread.sleep(3000);
}catch (InterruptedException e){
System.out.println("In main");
e.printStackTrace();
}
// 设置中断
t.interrupt();
}
Runnable runnable=()->{
int i=0;
try {
while (i<1000){
// 睡个半秒再执行
Thread.sleep(500);
System.out.println(i++);
}
}catch (InterruptedException e){
// 判断该阻塞线程是否还在
System.out.println(Thread.currentThread().isAlive());
// 判断该线程的中断标志位状态
System.out.println(Thread.currentThread().isInterrupted());
System.out.println("In Runnable");
e.printStackTrace();
}
};
}
- 结果
说明:
最后
- 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
- 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
- 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。
——我是冢狐,和你一样热爱编程。