2020 JAVA面试题

Obect的方法有哪些

protected Object clone() 创建并返回此对象的一个副本。
boolean equals(Object obj) 指示某个其他对象是否与此对象“相等”。
protected void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Class<? extendsObject> getClass() 返回一个对象的运行时类。
int hashCode() 返回该对象的哈希码值。
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
String toString() 返回该对象的字符串表示。
void wait() 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout) 导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos) 导致当前的线程等待,直到其他线程调用此对象的 notify()

为什么wait,notify,notifyAll不定义在Thread中,而是定义在Object中?

  1. wait 和 notify 不仅仅是普通方法或同步工具,更重要的是它们是 Java 中两个线程之间的通信机制。对语言设计者而言, 如果不能通过 Java 关键字(例如 synchronized)实现通信此机制,同时又要确保这个机制对每个对象可用, 那么 Object 类则是的合理的声明位置。记住同步和等待通知是两个不同的领域,不要把它们看成是相同的或相关的。同步是提供互斥并确保 Java 类的线程安全,而 wait 和 notify 是两个线程之间的通信机制。

2.每个对象都可上锁,这是在 Object 类而不是 Thread 类中声明 wait 和 notify 的另一个原因。

3.在 Java 中,为了进入代码的临界区,线程需要锁定并等待锁,他们不知道哪些线程持有锁,而只是知道锁被某个线程持有, 并且需要等待以取得锁, 而不是去了解哪个线程在同步块内,并请求它们释放锁。

4.Java 是基于 Hoare 的监视器的思想(http://en.wikipedia.org/wiki/…)。在Java中,所有对象都有一个监视器。

Thread的状态以及常用方法

Java中线程的状态分为6种。

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
    3.阻塞(BLOCKED):表示线程阻塞于锁。
    4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
    5.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。6. 终止(TERMINATED):表示该线程已经执行完毕。

Thread的常用方法:
String getName()  返回该线程的名称。
void setName(String name)  改变线程名称,使之与参数 name 相同。
int getPriority()   返回线程的优先级。
void setPriority(int newPriority)   更改线程的优先级。
boolean isDaemon()   测试该线程是否为守护线程。
void setDaemon(boolean on)  将该线程标记为守护线程或用户线程。

static void sleep(long millis)
void interrupt()  中断线程。
static void yield()  暂停当前正在执行的线程对象,并执行其他线程。
void join()  等待该线程终止。
void run()
void start()
从Object类继承来的方法  void notify() void wait()

什么是死锁

死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁

死锁产生的原因

可归结为如下两点:
a. 竞争资源
系统中的资源可以分为两类:
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

死锁产生的4个必要条件

产生死锁的必要条件:

1、互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
2、请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
4、环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法

预防死锁:

a.资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配b.其他的资源:(破坏请保持条件)
c.可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
d.资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

1、以确定的顺序获得锁
如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。

2、超时放弃
当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

避免死锁:

预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

检测死锁

1、首先为每个进程和每个资源指定一个唯一的号码;
2、然后建立资源分配表和进程等待表。

解除死锁

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

1、剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
2、撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

死锁检测

1、Jstack命令

jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

2、JConsole工具

Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗

线程池的创建方式

通常开发者都是利用Executors提供的通用线程池创建方法,去创建不同配置的线程池,主要区别在于不同的:
Executors目前提供了4种不同的线程池创建配置:

1、newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置时间超过60秒,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。

2、newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动线程数目,将在工作队列中等待空闲线程出现;如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads。

3、newSingleThreadExecutor(),它的特点在于工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不予许使用者改动线程池实例,因此可以避免改变线程数目。

4、newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。

多线程之CountDownLatch和CyclicBarrier的区别和用法

jdk1.5之后推出了java.util.concurrent包:
一.CountDownLatch的使用
CountDownLatch经常用于监听某些初始化操作,等初始化执行完毕后,再通知主线程继续工作。
CountDownLatch定义:
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。

下面我们来看下CountDownLatch的使用方法:
public class CountDownLatchTest {

public static void main(String[] args) {
	//实例化一个CountDownLatch对象
	final CountDownLatch countDownLatch = new CountDownLatch(2);
	//创建一个线程t1
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				System.out.println("进入线程t1  等待其他线程处理完成……");
				countDownLatch.await();
				System.out.println("t1线程继续执行……");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	},"t1").start();
	//创建一个线程t2
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				System.out.println("进入线程t3进行初始化操作……");
				Thread.sleep(2000);
				System.out.println("t3线程执行完毕。。。");
				countDownLatch.countDown();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	},"t2").start();
	//创建一个线程t3
	new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				System.out.println("进入线程t2进行初始化操作……");
				Thread.sleep(4000);
				System.out.println("t2线程执行完毕。。。");
				countDownLatch.countDown();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	},"t3").start();
}

}
执行结果如下:

进入线程t1 等待其他线程处理完成……
进入线程t3进行初始化操作……
进入线程t2进行初始化操作……
t3线程执行完毕。。。
t2线程执行完毕。。。
t1线程继续执行……
分析CountDownLatch:
运行程序我们会发现当我们在t1调用CountDownLatch的await()方法时,就好比我们调用了wait()方法,当前线程会处于阻塞状态,直到等到t2和t3完全执行完毕并且调用countDown()方法时,我们才能唤醒t1继续进行执行,CountDownLatch就好比一个计时器,我们可以让当前线程调用CountDownLatch中的await()方法进行等待,如果想让当前线程继续执行,我们必须让CountDownLatch获得初始化时候传入的构造参数个countDown()方法,我们才能继续执行。
CountDownLatch的使用场景:
在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
二.CyclicBarrier的使用
假设有一个场景,每个线程代表一个跑步的运动员,当运动员都准备好之后,才一起出发,只要有一个运动员还没有准备好,所有线程就一起等待。
CyclicBarrier的定义(来自百度):
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。

下面我们来看CyclicBarrier 的使用小Demo:
public class CyclicBarrireTest {

public static void main(String[] args) {
	CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
	ExecutorService executorService = Executors.newFixedThreadPool(3);
	try {
		executorService.execute(new Runner(cyclicBarrier, "张三"));
		executorService.execute(new Runner(cyclicBarrier, "李四"));
		executorService.execute(new Runner(cyclicBarrier, "王五"));
	} finally {
		executorService.shutdown();
	}
}

static class Runner implements Runnable{
	private String name;
	private CyclicBarrier cyclicBarrier;
	public Runner(CyclicBarrier cyclicBarrier,String name) {
		this.name = name;
		this.cyclicBarrier=cyclicBarrier;
	}
	
	@Override
	public void run() {
		try {
			Thread.sleep(1000*new Random().nextInt(5));
			System.out.println(Thread.currentThread().getName() +"已经准备好");
				cyclicBarrier.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
		System.err.println(Thread.currentThread().getName() +"出发!!");
	}
	
}

}
执行结果如下:
pool-1-thread-1已经准备好
pool-1-thread-3已经准备好
pool-1-thread-2已经准备好
pool-1-thread-2出发!!
pool-1-thread-1出发!!
pool-1-thread-3出发!!

CyclicBarrier 分析结果:
上述程序我们创建了一个线程池,这个线程池中有三个线程,每个线程都传递了一个相同的CyclicBarrier 对象和运动员的名字,我们Runner类中的run方法使每一个进来的运动员都休眠0-5秒的时间,然后调用await()方法,就是说每个线程进来都需要进行等待,直到所有的CyclicBarrier 都处于准备好了的状态,所有线程才能统一开始执行!
CyclicBarrier 使用场景
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
三.cyclicBarrier和CountDownLatch的区别
1、CountDownLatch简单的说就是一个线程等待,直到他所等待的其他线程都执行完成并且调用countDown()方法发出通知后,当前线程才可以继续执行。
2、cyclicBarrier是所有线程都进行等待,直到所有线程都准备好进入await()方法之后,所有线程同时开始执行!
3、CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
4,、CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。如果被中断返回true,否则返回false。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值