java面试总结(五)多线程

1、线程介绍:

   线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速。

2、线程与进程的区别:

  线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间,切每个线程都拥有单独的栈内存用来存储本地数据。

3、使用多线程

  从语言层面来讲有三种方式:(1)继承 java.lang.Thread 类。(2)直接实现Runnable接口来重写run()方法实现线程。(3)第三种 实现Callable<>接口并重写call方法。
  真正边吃中,建议直接实现Runnable接口,因为Java不支持类的多重继承,但允许实现多个接口。

4、Runnable和Callable的区别

  Runnable和Callable都代表那些要在不同的线程中执行的任务,Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。主要区别是Callable的 call() 方法可以有返回值和抛出异常,返回装载有计算结果的Future对象;而Runnable的run()方法没有这些功能。

5、线程的启动

  start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。

6、线程的生命周期

 (1)新建:用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable);注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。
 (2)就绪:处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
 (3)运行状态:处于运行状态的线程可以变成阻塞状态、就绪状态和死亡状态。
              当发现线程调用sleep方法、调用一个阻塞式IO方法(该方法未返回)、获取一个同步监视器(但更改同步监视器被其他线程所持有)、等待某个通知(notify)以及程序调用线程的suspend方法将线程挂起都会将运行状态变为阻塞状态。
              当线程的run()方法执行完,或者被强制性的终止,例如出现异常,或者调用了stop(),desyory()方法等等,就会从运行状态变为死亡状态。
 (4)阻塞:当发现线程调用sleep方法、调用一个阻塞式IO方法(该方法未返回)、获取一个同步监视器(但更改同步监视器被其他线程所持有)、等待某个通知(notify)以及程序调用线程的suspend方法将线程挂起都会将运行状态变为阻塞状态。
 (5)死亡:当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

7、线程同步

 (1)同步方法:即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
 (2)同步代码块:即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
 (3)成员变量同步:volatile是一个特殊的修饰符,只有成员变量才能使用它。保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
 (4)使用重入锁(Lock)实现线程同步,在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁。

8、线程池

 (1)线程池的作用:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗;提高响应速,当任务到达时,任务可以不需要等到线程创建就能立即执行;提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
 (2)使用Executors工厂类产生线程池,Executor线程池框架的最大优点是把任务的提交和执行解耦。客户端将要执行的任务封装成Task,然后提交即可。而Task如何执行客户端则是透明的。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果。
 (3)使用Java8增强的ForkJoinPool产生线程池,ForkJoinPool同ThreadPoolExecutor一样,也实现了Executor和ExecutorService接口。它使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。

9、interrupted 和 isInterruptedd方法的区别

  interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会,Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true;当中断线程调用静态方法Thread.interrupted()来 检查中断状态时,中断状态会被清零,而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识,简单的说就是任何抛 出InterruptedException异常的方法都会将中断状态清零。

10、submit() 和 execute()方法有什么不同

  两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

11、volatile 变量和 atomic 变量有什么不同

  Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

12、Java多线程中调用wait() 和 sleep()方法有什么不同

  Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要;wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

13、死锁

   产生死锁的四个必要条件:a、互斥条件,资源不能被共享,只能被同一个进程使用。b、请求与保持条件:已经得到资源的进程可以申请新的资源。c、非剥夺条件:已经分配的资源不能从相应的进程中被强制剥夺。d、循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程占用的资源。

参考资料:https://www.cnblogs.com/snow-flower/p/6114765.html

发布了11 篇原创文章 · 获赞 7 · 访问量 5036

猜你喜欢

转载自blog.csdn.net/rg201612/article/details/104769212
今日推荐