腾讯百度聚美Java面试题集总全集九.docx
《腾讯百度聚美Java面试题集总全集九.docx》由会员分享,可在线阅读,更多相关《腾讯百度聚美Java面试题集总全集九.docx(10页珍藏版)》请在冰豆网上搜索。
![腾讯百度聚美Java面试题集总全集九.docx](https://file1.bdocx.com/fileroot1/2023-1/6/8c23cb6c-eb48-448e-94de-a7da7fe0078b/8c23cb6c-eb48-448e-94de-a7da7fe0078b1.gif)
腾讯XX聚美Java面试题集总全集九
56、TreeMap和TreeSet在排序时如何比较元素?
Collections工具类中的sort()方法如何比较元素?
答:
TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。
Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。
例子1:
publicclassStudentimplementsComparable{ privateStringname; //姓名
privateintage; //年龄
publicStudent(Stringname,intage){ this.name=name; this.age=age;
} @Override
publicStringtoString(){ return"Student[name="+name+",age="+age+"]";
} @Override
publicintcompareTo(Studento){ returnthis.age-o.age;//比较年龄(年龄的升序)
}
}
importjava.util.Set;
importjava.util.TreeSet;classTest01{ publicstaticvoidmain(String[]args){
Setset=newTreeSet<>(); //Java7的钻石语法(构造器后面的尖括号中不需要写类型)
set.add(newStudent("HaoLUO",33)); set.add(newStudent("XJWANG",32)); set.add(newStudent("BruceLEE",60)); set.add(newStudent("BobYANG",22)); for(Studentstu:
set){
System.out.println(stu);
}// 输出结果:
// Student[name=BobYANG,age=22]// Student[name=XJWANG,age=32]// Student[name=HaoLUO,age=33]// Student[name=BruceLEE,age=60]
}
}
例子2:
publicclassStudent{ privateStringname; //姓名
privateintage; //年龄
publicStudent(Stringname,intage){ this.name=name; this.age=age;
} /**
*获取学生姓名
*/
publicStringgetName(){ returnname;
} /**
*获取学生年龄
*/
publicintgetAge(){ returnage;
}
@Override publicStringtoString(){ return"Student[name="+name+",age="+age+"]";
}
}
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.Comparator;
importjava.util.List;classTest02{ publicstaticvoidmain(String[]args){
Listlist=newArrayList<>(); //Java7的钻石语法(构造器后面的尖括号中不需要写类型)
list.add(newStudent("HaoLUO",33)); list.add(newStudent("XJWANG",32)); list.add(newStudent("BruceLEE",60)); list.add(newStudent("BobYANG",22)); //通过sort方法的第二个参数传入一个Comparator接口对象
//相当于是传入一个比较对象大小的算法到sort方法中
//由于Java中没有函数指针、仿函数、委托这样的概念
//因此要将一个算法传入一个方法中唯一的选择就是通过接口回调
Collections.sort(list,newComparator(){
@Override publicintcompare(Studento1,Studento2){ returno1.getName().compareTo(o2.getName()); //比较学生姓名
}
}); for(Studentstu:
list){
System.out.println(stu);
}// 输出结果:
// Student[name=BobYANG,age=22]// Student[name=BruceLEE,age=60]// Student[name=HaoLUO,age=33]// Student[name=XJWANG,age=32]
}
}
57、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?
答:
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。
wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(waitpool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lockpool),如果线程重新获得对象的锁就可以进入就绪状态。
补充:
可能不少人对什么是进程,什么是线程还比较模糊,对于为什么需要多线程编程也不是特别理解。
简单的说:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。
线程的划分尺度小于进程,这使得多线程程序的并发性高;进程在执行时通常拥有独立的内存单元,而线程之间可以共享内存。
使用多线程的编程通常能够带来更好的性能和用户体验,但是多线程的程序对于其他程序是不友好的,因为它可能占用了更多的CPU资源。
当然,也不是线程越多,程序的性能就越好,因为线程之间的调度和切换也会浪费CPU时间。
时下很时髦的Node.js就采用了单线程异步I/O的工作模式。
58、线程的sleep()方法和yield()方法有什么区别?
答:
①sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
②线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
59、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
答:
不能。
其它线程只能访问该对象的非同步方法,同步方法则不能进入。
因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。
60、请说出与线程同步以及线程调度相关的方法。
答:
-wait():
使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
-sleep():
使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
-notify():
唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
-notityAll():
唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
补充:
Java5通过Lock接口提供了显式的锁机制(explicitlock),增强了灵活性以及对线程的协调。
Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;此外,Java5还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。
在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。
下面的例子演示了100个线程同时向一个银行账户中存入1元钱,在没有使用同步机制和使用同步机制情况下的执行情况。
∙
银行账户类:
∙
∙
/**
*银行账户
*@author骆昊
*
*/publicclassAccount{ privatedoublebalance; //账户余额
/**
*存款
*@parammoney存入金额
*/
publicvoiddeposit(doublemoney){ doublenewBalance=balance+money; try{
Thread.sleep(10); //模拟此业务需要一段处理时间
} catch(InterruptedExceptionex){
ex.printStackTrace();
}
balance=newBalance;
} /**
*获得账户余额
*/
publicdoublegetBalance(){ returnbalance;
}
}
∙
存钱线程类:
∙
∙
/**
*存钱线程
*@author骆昊
*
*/publicclassAddMoneyThreadimplementsRunnable{ privateAccountaccount; //存入账户
privatedoublemoney; //存入金额
publicAddMoneyThread(Accountaccount,doublemoney){ this.account=account; this.money=money;
} @Override
publicvoidrun(){
account.deposit(money);
}
}
∙
测试类:
∙
∙
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;publicclassTest01{ publicstaticvoidmain(String[]args){
Accountaccount=newAccount();
ExecutorServiceservice=Executors.newFixedThreadPool(100); for(inti=1;i<=100;i++){
service.execute(newAddMoneyThread(account,1));
}
service.shutdown(); while(!
service.isTerminated()){}
System.out.println("账户余额:
"+account.getBalance());
}
}
在没有同步的情况下,执行结果通常是显示账户余额在10元以下,出现这种状况的原因是,当一个线程A试图存入1元的时候,另外一个线程B也能够进入存款的方法中,线程B读取到的账户余额仍然是线程A存入1元钱之前的账户余额,因此也是在原来的余额0上面做了加1元的操作,同理线程C也会做类似的事情,所以最后100个线程执行结束时,本来期望账户余额为100元,但实际得到的通常在10元以下(很可能是1元哦)。
解决这个问题的办法就是同步,当一个线程对银行账户存钱时,需要将此账户锁定,待其操作完成后才允许其他的线程进行操作,代码有如下几种调整方案:
∙
在银行账户的存款(deposit)方法上同步(synchronized)关键字
∙
∙
/**
*银行账户
*@author骆昊
*
*/publicclassAccount{ privatedoublebalance; //账户余额
/**
*存款
*@parammoney存入金额
*/
publicsynchronizedvoiddeposit(doublemoney){ doublenewBalance=balance+money; try{
Thread.sleep(10); //模拟此业务需要一段处理时间
} catch(InterruptedExceptionex){
ex.printStackTrace();
}
balance=newBalance;
} /**
*获得账户余额
*/
publicdoublegetBalance(){ returnbalance;
}
}
∙
在线程调用存款方法时对银行账户进行同步
∙
∙
/**
*存钱线程
*@author骆昊
*
*/publicclassAddMoneyThreadimplementsRunnable{ privateAccountaccount; //存入账户
privatedoublemoney; //存入金额
publicAddMoneyThread(Accountaccount,doublemoney){ this.account=account; this.money=money;
} @Override
publicvoidrun(){ synchronized(account){
account.deposit(money);
}
}
}
∙
通过Java5显示的锁机制,为每个银行账户创建一个锁对象,在存款操作进行加锁和解锁的操作
∙
∙
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;/**
*银行账户
*
*@author骆昊
*
*/publicclassAccount{ privateLockaccountLock=newReentrantLock(); privatedoublebalance;//账户余额
/**
*存款
*
*@parammoney
* 存入金额
*/
publicvoiddeposit(doublemoney){
accountLock.lock(); try{ doublenewBalance=balance+money; try{
Thread.sleep(10);//模拟此业务需要一段处理时间
} catch(InterruptedExceptionex){
ex.printStackTrace();
}
balance=newBalance;
} finally{
accountLock.unlock();
}
} /**
*获得账户余额
*/
publicdoublegetBalance(){ returnbalance;
}
}
按照上述三种方式对代码进行修改后,重写执行测试代码Test01,将看到最终的账户余额为100元。
当然也可以使用Semaphore或CountdownLatch来实现同步。