【java面试经(架构师&设计师)-第5课】JAVA基础之多线程(一)

技术清单

申明:本文属于整理加工原创,部分举证材料来自于网络,仅用于学习参考。

本文主要介绍java数据结构相关知识,通过本文讲解,你可以明白:

1、进程和线程的区别?

2、创建线程的方法,以及他们之间的区别是什么?用Runnable还是Thread?

3、Thread 类中的start() 和 run() 方法有什么区别?

4、什么是FutureTask?

5、Java线程的状态,BLOCKED和WAITING有什么区别?

6、HashMap的数据结构是什么?如何实现的。和HashTable,HashMap,TreeMap,ConcurrentHashMap的区别?

7、ArrayList是如何实现的,Vector、ArrayList和LinkedList的区别?ArrayList如何实现扩容。

8、String,StringBuffer,StringBuilder 的区别是什么?String为什么是不可变的?

9、Java中的volatile 关键字是什么?ThreadLocal代表什么?

10、Java线程池的实现原理,keepAliveTime等参数的作用?

技术解析

一、进程和线程的区别?

进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

那么进程和线程的区别到底是什么呢?

1、一个进程包含一个或多个线程。它可以是一段完整的代码或部分程序的动态执行,系统资源分配与调度的基本单位;

2、线程是CPU调度与运行的基本单位,它是一组指令的集合或是程序的特殊段,它是轻量级的进程,他基本上没有占用多少系统资源,但是线程它还有自己的独立资源,比如栈,程序计数器,寄存器并且一个线程可以和其他在同一个进程中的线程共享进程资源。

3、线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行必须依存在应用程序中,由应用程序提供多个线程执行控制。

4、从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。

二、创建线程的方法,以及他们之间的区别是什么?用Runnable还是Thread?

创建线程的方法有:继承Thread类创建线程、实现Runnable接口创建线程、使用Callable和Future创建线程,共3种。

我们来看看几种创建线程的流程。

1、通过继承Thread类来创建并启动多线程的一般步骤如下:

1)定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体;

2)创建Thread子类的实例,也就是创建了线程对象;

3)启动线程,即调用线程的start()方法。

public class MyThread extends Thread{//继承Thread类
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}

2、通过实现Runnable接口创建并启动线程一般步骤如下

1)定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体;

2)创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象;

3)第三部依然是通过调用线程对象的start()方法来启动线程。

public class MyThread2 implements Runnable {//实现Runnable接口
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread2 myThread=new MyThread2();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者    new Thread(new MyThread2()).start();
  }

}

3、Callable的方式

首先讲一下Callable涉及的概念:

1)Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大,和Runnable接口不一样。

2)call()方法可以有返回值

3)call()方法可以声明抛出异常

4)Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target;

实现具体步骤如下:

1)创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象);

2)使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值;

3)使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口);

4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Main {
	public static void main(String[] args) {
		/** 使用Lambda表达式创建Callable对象 */
		// 使用FutureTask类来包装Callable对象
		FutureTask<Integer> future = new FutureTask<Integer>((Callable<Integer>) () -> {
			return 5;
		});
		// 实质上还是以Callable对象来创建并启动线程
		new Thread(future, "有返回值的线程").start();
		try {
			// get()方法会阻塞,直到子线程执行结束才返回
			System.out.println("子线程的返回值:" + future.get());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

4、现在来讲讲他们的区别

1)采用实现Runnable、Callable接口的方式创建多线程时

优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

2)使用继承Thread类的方式创建多线程时

优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。

3)Runnable和Callable的区别

a)Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

b)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

c) call方法可以抛出异常,run方法不可以。

d)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果

4)那么什么时候使用Runnable,什么时候使用Thread呢?

实现Runnable接口是首选方法。在这里,并不需要修改Thread类的行为。你只是给线程一些东西运行。这意味着组合是最好的方式。实现Runnable使你的类更灵活,如果继承Thread类,那么所做的操作总是处于一个线程中。然而如果你采用实现Runnable接口,可以在一个线程中运行它,或者将它传递给某种执行器(executor),或者只是将它作为一个单线程应用程序中的任务传递给它。通过继承Thread类,每个线程都有一个相关联的唯一对象,而实现Runnable接口,多线程可以共享同一个Runnable实例

三、Thread 类中的start() 和 run() 方法有什么区别?

首先我们回答这问题之前,有必要要了解一下线程的生命周期。在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡,如下图所示:

1、建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

2、就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

3、运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

4、阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1)等待阻塞-运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2)同步阻塞- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3)其他阻塞- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

说明:其实join()内部也是wait()实现的,但是与wait()是有区别的。后面的知识点会讲到。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

现在我们来看看他们的区别:

1、start():该方法是在当前线程中启动一个新的线程,而新启动的线程会调用run()方法,同时该方法不能重复调用;通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。 然后通过

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值