面试研发岗位的小伙伴可能发现了,面试官经常会提问有关“线程”的问题,比如,线程是什么?多线程的常见应用场景是什么?创建线程的方式有哪几种?这些方式分别有什么优点?等等。
线程问题这么受面试考官青睐,重要程度自然不必说啦。今天,我们就来讲一下“线程”的问题。
问题一:线程是什么?
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中;
一个进程可以并发多个线程,每条线程并行执行不同的任务;
多线程程序设计的好处是提高了程序的执行吞吐率和执行效率。
问题二:多线程的常见应用场景
后台任务,例如:定时向大量用户推送信息例如广告邮件,不胜其扰,大家一定有体会
异步处理,例如:批量I/O操作,发微博、记录日志等
分布式计算,共享稀有资源和平衡负载
web服务器本身,例如:Tomcat
问题三:创建线程有几种方式?
使用多线程第一步就是创建线程,今天给大家分享6种创建线程的方式。
方式一:继承Thread类并重写run()方法
public class ThreadDemo1 extends Thread
{
private String name;
public ThreadDemo1 (String name)
{
this.name=name;
}
@Override
public void run()
{
System.out.println(this.name+"线程运行了");
}
public static void main(String[] args)
{
new ThreadDemo1(“线程一”).start();
new ThreadDemo1(“线程二”).start();
}
}
总结:这是最简单的创建方式,但弊端是占用了唯一一次继承机会,因为Java是单继承,如果这个类必须继承其它类,就无法采用这种创建方式了。
方式二: 实现Runnable接口
public class ThreadDemo2 implements Runnable
{
@Override public void run()
{
System.out.println(Thread.currentThread().getName() + "线程运行了");
}
public static void main(String[] args)
{
new Thread(“线程1”,new ThreadDemo2()).start();
new Thread(“线程2”,new ThreadDemo2()).start();
}
}
总结:这种创建方式最大的优点是代码简洁,只需关注线程代码本身即可;缺点是代码复用性较差。如果线程代码只使用一次,可以考虑这种方式,现在多数使用lambda表达式(JAVA8+)。
方式三: 匿名内部类
public classThreadDemo3
{
public static void main(String[] args)
{ // 第一种Thread匿名子类,重写Trun()方法
new Thread() {
@Override public void run()
{
System.out.println(" 匿名Thread子类,线程运行了");
} }.start();
// 第二种Runnable匿名实现类,实现run()方法
new Thread(new Runnable() {
@Override public void run()
{
System.out.println( " Runnable匿名实现类,线程运行了");
} }).start();
// 第三种使用lambda表达式,简化代码
new Thread(()->{
System.out.println("Lambda表达式线程类,线程运行了");
}).start();
}
}
总结:这种创建方式最大的优点是代码简洁,只需关注线程代码本身即可;缺点是代码复用性较差。如果线程代码只使用一次,可以考虑这种方式,现在多数使用lambda表达式(JAVA8+)。
方式四: 使用Timer定时器(java.util.Timer)
public class ThreadDemo4
{
public static void main(String[] args)
{ //创建Timer定时器
Timer timer = new Timer();
// 每隔1秒执行一次
timer.schedule(new TimerTask()
{
@Override
public void run()
{
System.out.println(Thread.currentThread().getName() + " is running");
}
}, 0 , 1000);
}
}
总结:TimerTask实现了Runnable接口,Timer定时器可以安排线程“执行一次”或定期“执行多次”。在实际开发过程中,经常需要周期性的操作,如每几分钟执行某项操作等。对于这类需求,最方便高效的实现方式就是使用java.util.Timer工具类。
方式五: 使用线程池
class MyThread implements Runnable
{
@Override
public void run()
{
System.out.println(Thread.currentThread().getName()+“正在运行”);
}
}
public class ThreadDemo5
{
public static void main(String[] args)
{
//池中有十个线程
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++)
{
threadPool.execute(new MyThread());
}
}
}
总结:线程池是多个线程的集合,使用线程池我们不需要自己创建线程,将线程任务提交给线程池即可。线程池有以下两大优点:1.可重用已有的线程继续执行任务,减少线程创建和销毁时造成的损耗从而提高系统响应速度;2.通过对可执行线程进行合理管理,根据系统承受能力调整可运行线程数量的大小等。最佳实践Tomcat服务器处理用户请求的线程,就是使用线程池进行管理。
方式六: 实现Callable接口(java.util.concurrent.Callable)
public class ThreadDemo6 implements Callable
{
@Override
public String call() throws Exception
{ //实现call方法
System.out.println( " is running");
return “线程执行后结果”;
}
public static void main(String[] args) throws Exception
{
FutureTask task = new FutureTask<>(new ThreadDemon6());
new Thread(task).start();
System.out.println("等待线程执行结束");
String result = task.get();
System.out.println("线程执行结果:" + result);
}
}
总结:Java多线程的核心方法run是没有返回值的,如需run方法执行后的结果,必须等待run方法计算完,无论计算过程多么耗时。而Future模式恰恰能解决这一困境。实现Callabe接口,可以获得线程执行后的结果。
上面介绍了那么多创建线程的方式,总结而言,分为两大类:
继承Thread类并重写run()方法
实现Runnable接口的run()方法
上面的内容,你学会了吗?
彩蛋: 某大型互联网公司面试真题
public class ThreadExam
{
public static void main(String[] args)
{
new Thread(
()-> { System.out.println("Runnable: 运行" ); }
)
{
@Override public void run() { System.out.println("Thread: 运行"); }
}.start();
}
}