1.JAVA创建线程的方法 - 面试题引发的思考
可以说是面试必答题了,一般性答案是三种:
暂时先不考虑线程池
- 继承
Thread类,重写run方法
public class Test {
public static void main(String[] args) {
new ThreadTest().start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.print(Thread.currentThread().getName());
}
}
- 实现
Runable方法
public class Test {
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
new Thread(runnableTest, "线程1").start();
new Thread(runnableTest, "线程2").start();
}
}
class RunnableTest implements Runnable{
@Override
public void run() {
System.out.print(Thread.currentThread().getName());
}
}
- 使用
Callable和Future
public class Test {
public static void main(String[] args){
MyCallable callable = new MyCallable();
FutrueTask<String> task = new FutureTask(callable);
new Thread(task).start();
try {
System.out.println(task.get())
} catch(Exception e){}
}
}
class MyCallable implements Callable {
@Override
public String call() throw Exception {
return Thread.currentThread().geName();
}
}
在JAVA中线程的控制和业务的执行是分离的,可以看到三个创建线程的方法都包含new Thread().start(),实际上只有线程类的start()方法会调用native start0()去创建一个新的线程,不管是实现Runnable还是通过FutrueTask都是在启动线程后执行方法,这个方法一般来说可以执行任意操作take any action whatsoever
线程的实例化都做了什么
Thread的实例化入参(从左到右)包括线程组、Runnable被调用run方法的对象(可用lambda表达式)、线程名、线程占用的堆栈大小(默认由虚拟机控制)、上下文
初始化init方法步骤
- 判断线程名是否为空,并赋值线程名; 如果为空,抛出空指针异常
name cannot be null - 获取当前线程
Thread parent = currentThread()
public static native Thread currentThread(){}返回当前正在执行的线程对象的引用
- 获取当前系统的安全组管理器
SecurityManager security = System.getSecurityManager() - 判断是否指定线程组(树状数据结构),即入参的
ThreadGroup, 如果指定了线程组,调用线程组的校验方法checkAccess()(校验当前线程是否有权限修改对应线程组);没有指定线程组,则用安全组内的线程组作为当前线程组或者使用当前线程的线程组 - 如果安全组存在校验安全组的使用权限
security.checkPermission() - 增加线程组内计数
addUnstarted() - 获取当前线程的上下文(通过当前线程的
ClassLoader) - 赋值
Runnable对象, 判断是否守护线程,并提供优先级,当前线程ID累加nextThreadID()生成新线程tid
线程的start()都做了什么
public synchronized void start() {
// 判断线程状态是否是未启动状态, 0状态对应New状态
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 当前线程添加到线程组的线程列表内,减少线程组计数(init时通过addUnstarted()增加的计数)
group.add(this);
boolean started = false;
try {
// 调用本地的start0方法创建一个线程
start0();
started = true;
// 守护线程不一定执行finally(init时判断守护线程的结果)
}finally {
try {
if(!started) {
group.threadStartFaild(this);
}
} catch (Throwable ignore){}
}
}
总结
通过继承Thread或者实现Runnable接口重写run()方法,写入在新建线程后要执行的业务逻辑,线程对象执行start()方法新建一个线程,在线程启动成功后会自动调用线程对象的run()方法,至于为什么可以通过实现Callable并使用FutureTask,是因为Callable和Runnable类似,Executors类包含从其他常见形式转换为Callable类的实用方法,并且FutureTask实现的接口RunnableFuture接口继承于Runnable
附录:
Callable的call方法与Runnable的run方法的区别
call方法执行后可以有返回值,run方法没有返回值call方法可以抛出异常,run方法不可以- 运行
Callable任务可以拿到一个Future对象,表示异步计算的结果 。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果