java线程面试问题整理总结

目录

1.多线程中的 i++线程安全吗?为什么?

不安全。i++不是原子性操作。i++分为读取 i 值,对 i 值加一,再赋值给 i++,执行过程中任何一步都是有可能被其他线程抢占。

2.如何线程安全的实现一个计数器?

可使用加锁,比如 synchronized 或者 lock。也可以使用 Concurrent 包下的原子类。

3.多线程同步的方法

可以使用 synchronized、lock、volatile 和 ThreadLocal 来实现同步。

4.介绍一下生产者消费者模式?


生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。
优点:支持并发、解耦。

5.线程,进程,然后线程创建有很大开销,怎么优化?

可以使用线程池来进行优化

6.线程池运行流程,参数,策略

线程池主要就是指定线程池核心线程数大小,最大线程数,存储的队列,拒绝策略,空闲线 程存活时长。当需要任务大于核心线程数时候,就开始把任务往存储任务的队列里,当存储队列满了的话,就开始增加线程池创建的线程数量,如果当线程数量也达到了最大,就开始执行拒绝策略,比如说记录日志,直接丢弃,或者丢弃最老的任务。

7.创建线程的方法,哪个更好,为什么?

需要从 Java.lang.Thread 类派生一个新的线程类,重载它的 run()方法;
实现 Runnalbe 接口,重载 Runnalbe 接口中的 run()方法。
实现 Runnalbe 接口更好,使用实现 Runnable 接口的方式创建的线程可以处理同一资源,从而实现资源的共享。

8.Java 启动一个线程有哪几种方式?

  1. 继承 Thread 类 ,重载 run()方法
  1. 实现 Runnable 接口,重载 Runnalbe 接口中的 run()方法

3.即实现 Runnable 接口,也继承 Thread 类,并重写 run 方法

9.Java 中有几种线程池?

1、newFixedThreadPool 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一 个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
2、newCachedThreadPool 创建一个可缓存的线程池。这种类型的线程池特点是: 1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为 Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。 2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个 工作线程。
3、newSingleThreadExecutor 创建一个单线程化的 Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 。
4、newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

10.线程池的好处?

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

11.启动线程有哪几种方式,线程池有哪几种?

①启动线程有如下三种方式:
一、继承 Thread 类创建线程类
1)定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的体就代表了线程要完成的任务。因此把 run()方法称为执行体。
2)创建 Thread 子类的实例,即创建了线程对象。
3)调用线程对象的 start()方法来启动该线程。

代码1

package com.bjpowernode.demo04;

public class FirstThreadTest extends Thread {
	// 重写 run 方法,run 方法的方法体就是现场执行体
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(currentThread().getName() + " " + i);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			if (i == 20) {
				new FirstThreadTest().start();
				new FirstThreadTest().start();
			}
		}
	}
}

代码1中的 Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。

二、通过 Runnable 接口创建线程类
(1)定义 runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体是该线程的线程执行体。
(2)创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象, 该 Thread 对象才是真正的线程对象。
(3)调用线程对象的 start()方法来启动该线程。

代码2

package com.bjpowernode.demo02.p1;

//1)定义类实现Runnable
public class subThread implements Runnable {
	// 2)重写run()方法run方法,run()方法体就是子线程要换行的代码
	@Override
	public void run() {
		// 打印100行字符串
		for (int i = 1; i <= 100; i++) {
			System.out.println("sub Thread:" + i);
		}
	}

	public static void main(String[] args) {
		// 3创建线程对象
		subThread target = new subThread();// 创建Runable接口的实现类对象
		Thread t2 = new Thread(target);// 把实现类对象作为构造方法的实参
		// 4)开启新的线程
		t2.start();
		// main线程
		for (int i = 1; i <= 100; i++) {
			System.out.println("main==>" + i);
		}
	}
}

三、通过 Callable 和 Future 创建线程
(1)创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体, 并且有返回值。
(2)创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。
(3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

package com.bjpowernode.demo02.p2;
import java.util.Random;
import java.util.concurrent.Callable;
//1)定义类实现Callable接口,Callable接口的泛型指定call()方法的返回值类型
public class Prime2 implements Callable<Integer> {
	// 2)重写call()方法
	@Override
	public Integer call() throws Exception {
		Integer num = new Random().nextInt(100);
		return num;
	}
}
package com.bjpowernode.demo02.p2;
import java.lang.annotation.Target;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * 创建线程的方式3,主要用在线程池当中(存放线程的池子)
 *2020年6月7日下午8:26:41
 */
public class Test {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//3)创建线程对象
		Prime2 prime2 = new Prime2();//创建Callable接口实现类对象
		FutureTask<Integer> target = new FutureTask<>(prime2);
		//FutureTask类实现了RunableFuture接口,RunableFuture接口继承了Runable接口
		//FutureTask类就是Runable接口的实现类
		Thread t3 = new Thread(target);
		//4)开启线程
		t3.start();
		//在main线程中,可以获得子线程返回的数据
		System.out.println("main....");
		System.out.println("num-->"+target.get());
		System.out.println("mainend....");
	}
}

②线程池的种类有: Java 通过 Executors 提供四种线程池,分别为:
newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

12.如何理解 Java 多线程回调方法?

所谓回调,就是客户程序 C 调用服务程序 S 中的某个方法 A,然后 S 又在某个时候反过来调 用 C 中的某个方法 B,对于 C 来说,这个 B 便叫做回调方法。

13.创建线程有几种不同的方式?你喜欢哪一种?为什么?

有三种方式可以用来创建线程:
继承 Thread 类
实现 Runnable 接口 、
应用程序可以使用 Executor 框架来创建线程池
实现 Runnable 接口这种方式更受欢迎,因为这不需要继承 Thread 类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而 Java 不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。

14.概括的解释下线程的几种可用状态。

  1. 新建( new ):新创建了一个线程对象。
  2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
  3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) , 执行程序代码。 4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种: (一). 等待阻塞:运行( running )的线程执行 wait ()方法, JVM 会把该线程放入等待队列( waitting queue )中。 (二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁被别的线程占用, 则 JVM 会把该线程放入锁池( lock pool )中。 (三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join () 方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。 5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

15.同步方法和同步代码块的区别是什么?

同步方法使用synchronized关键字修饰的方法就同步方法,把整个方法体都作为同步代码块。
如果修饰实例方法,默认的锁对象是this对象
如果修饰静态方法,默认的锁对象是当前类的运行时类对象

16.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同 步?

监视器和锁在 Java 虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一 个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。

17.sleep() 和 wait() 有什么区别?

sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程, 但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。
wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

18.同步和异步有何异同,在什么情况下分别使用他们?举例说明。

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很 多情况下采用异步途径往往更有效率。

19.启动一个线程是用 run()还是 start()?

启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。

20.请说出你所知道的线程同步的方法

wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。 sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException 异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。
notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。

21.多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

多线程有两种实现方法,分别是继承Thread类与实现Runnable接口同步的实现方面有两种, 方法分别是 synchronized,wait 与 notify。

22.java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop() 和 suspend()方法为何不推荐使用?

有两种实现方法,分别是继承 Thread 类与实现 Runnable 接口用 synchronized 关键字修饰同步方法,反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源, 就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个标志,指出线 程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。

23.线程的 sleep()方法和 yield()方法有什么区别?

1.sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
2. 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready) 状态;
3. sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
4. sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性。

24.请说出与线程同步以及线程调度相关的方法。

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一 个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
    通过 Lock 接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。 Lock 接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了 newCondition() 方法来产生用于线程之间通信的 Condition 对象;此外,Java 5 还提供了信号量机制(semaphore), 信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用 Semaphore 对象的 acquire()方法);在完成对资源的访问后,线程必 向信号量归还许可(调用 Semaphore 对象的 release()方法)。

25.举例说明同步和异步

如果系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库操作中的排他锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程, 在很多情况下采用异步途径往往更有效率。事实上,所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。

26.什么是线程池(thread pool)?

在面向对象编程中,创建和销毁对象是很浪费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是 一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建, 使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情 况下,因此在工具类 Executors 面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:

  • newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  • newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大容量。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要 的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可 以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
  • newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任 务的需求。
  • newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任 务的需求。

27.说说线程的基本状态以及状态之间的关系?

其中 Running 表示运行状态
Runnable 表示就绪状态(万事俱备,只欠 CPU)
Blocked 表 示阻塞状态,阻塞状态又有多种情况,可能是因为调用 wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等待池,或者是调用了 sleep()方法或 join()方法等待休眠或其他线程结束,或是因为发生了 I/O 中断。

28.如何保证线程安全?

通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值