当前位置 博文首页 > weixin_34071713的博客:留学生大佬面经
作者:bobbyparadise
链接:https://www.nowcoder.com/discuss/84899?type=0&order=4&pos=3&page=1
来源:牛客网
拼多多面试:
1,·??CHROME给每个tab开了进程,为什么? 其实就是问线程与进程区别 :
·?·??进程是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。
·?线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈 。
·?调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
·?并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
·?拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
·?具体见Java线程与线程、进程与进程之间通信方式
·?关于进程和线程,首先从定义上理解就有所不同:
1.?进程是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。
2.?线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈 。
·
他们之间的关系
·
1.?一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2.?资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3.?线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4.?处理机分给线程,即真正在处理机上运行的是线程。
5.?线程是指进程内的一个执行单元,也是进程内的可调度实体。
·
从三个角度来剖析二者之间的区别
·
1.?调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2.?并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3.?拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
·?共享变量
·?wait/notify机制
·?Lock/Condition机制
·?管道
·?线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象,并提供了set和check方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class MySignal{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; } } |
·?线程A和B必须获得指向一个MySignal共享实例的引用,以便进行通信。如果它们持有的引用指向不同的MySingal实例,那么彼此将不能检测到对方的信号。需要处理的数据可以存放在一个共享缓存区里,它和MySignal实例是分开存放的。
·?为了实现线程通信,我们可以使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定。这三个方法必须由同步监视器对象来调用,这可分成两种情况:
o?对于使用synchronized修饰的同步方法,因为该类的默认实例是(this)就是同步监视器,所以可以直接调用这三使用个方法。
o?对于synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象调用这三个方法。
·
假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱。
我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:
·
·
首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用synchronized关键字)。
·
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | public class Account { private String accountNo; private double balance; //标识账户中是否有存款的旗标 private boolean flag=false; public Account() { super(); } public Account(String accountNo, double balance) { super(); this.accountNo = accountNo; this.balance = balance; } public synchronized void draw (double drawAmount){ try { if(!flag){ this.wait(); }else { //取钱 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount); balance=balance-drawAmount; System.out.println("余额 : "+balance); //将标识账户是否已有存款的标志设为false flag=false; //唤醒其它线程 this.notifyAll(); } } catch (Exception e) { e.printStackTrace(); } } public synchronized void deposit(double depositAmount){ try { if(flag){ this.wait(); } else{ System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount); balance=balance+depositAmount; System.out.println("账户余额为:"+balance); flag=true; //唤醒其它线程 this.notifyAll(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } } |
·?接下来创建两个线程类,分别为取钱和存钱线程!
·?取钱线程类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class DrawThread implements Runnable { private Account account; private double drawAmount; public DrawThread(Account account, double drawAmount) { super(); this.account = account; this.drawAmount = drawAmount; } public void run() { for(int i=0;i<100;i++){ account.draw(drawAmount); } } } |
·?存钱线程类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class depositThread implements Runnable{ private Account account; private double depositAmount; public depositThread(Account account, double depositAmount) { super(); this.account = account; this.depositAmount = depositAmount; } public void run() { for(int i=0;i<100;i++){ account.deposit(depositAmount); } } } |
·?最后我们测试一下这个取钱和存钱的操作
1 2 3 4 5 6 7 8 9 10 | public ?class TestDraw { public static void main(String[] args) { //创建一个账户 Account account=new Account(); new Thread(new DrawThread(account, 800),"取钱者").start(); new Thread(new depositThread(account, 800),"存款者甲").start(); new Thread(new depositThread(account, 800),"存款者乙").start(); new Thread(new depositThread(account, 800),"存款者丙").start(); } } |
·?大致的输出结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 |
·?如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.
·?当使用LOCK对象保持同步时,Java为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。
·?我们就拿Account类进行稍微的修改 一下吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { //显示定义Lock对象 private final Lock lock=new ReentrantLock(); //获得指定Lock对象对应的条件变量 private final ?Condition con=lock.newCondition(); private String accountNo; private double balance; //标识账户中是否有存款的旗标 private boolean flag=false; public Account() { super(); } public Account(String accountNo, double balance) { super(); this.accountNo = accountNo; this.balance = balance; } public void draw (double drawAmount){ //加锁 lock.lock(); try { if(!flag){ // ???????????this.wait(); con.await(); }else { //取钱 System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount); balance=balance-drawAmount; System.out.println("余额 : "+balance); //将标识账户是否已有存款的标志设为false flag=false; //唤醒其它线程 // ??????????????this.notifyAll(); con.signalAll(); } } catch (Exception e) { e.printStackTrace(); } finally{ lock.unlock(); } } public void deposit(double depositAmount){ //加锁 lock.lock(); try { if(flag){ // ?????????????this.wait(); con.await(); } else{ System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount); balance=balance+depositAmount; System.out.println("账户余额为:"+balance); flag=true; //唤醒其它线程 // ???????????????this.notifyAll(); con.signalAll(); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); }finally{ lock.unlock(); } } } |
·?输出结果和上面是一样的!只不过这里显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程,唤醒指定线程!
·
管道流是JAVA中线程通讯的常用方式之一,基本流程如下:
·
1.
创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis
2.
3.
将pos和pis匹配,pos.connect(pis);
4.
5.
将pos赋给信息输入线程,pis赋给信息获取线程,就可以实现线程间的通讯了
6.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; public class testPipeConnection { public static void main(String[] args) { /** * 创建管道输出流 */ PipedOutputStream pos = new PipedOutputStream(); /** * 创建管道输入流 */ PipedInputStream pis = new PipedInputStream(); try { /** * 将管道输入流与输出流连接 此过程也可通过重载的构造函数来实现 */ pos.connect(pis); } catch (IOException e) { e.printStackTrace(); } /** * 创建生产者线程 */ Producer p = new Producer(pos); /** * 创建消费者线程 */ Consumer1 c1 = new Consumer1(pis); /** * 启动线程 */ p.start(); c1.start(); } } /** * 生产者线程(与一个管道输入流相关联) * */ class Producer extends Thread { private PipedOutputStream pos; public Producer(PipedOutputStream pos) { this.pos = pos; } public void run() { int i = 0; try { while(true) { this.sleep(3000); pos.write(i); i++; } } catch (Exception e) { e.printStackTrace(); } } } /** * 消费者线程(与一个管道输入流相关联) * */ class Consumer1 extends Thread { private PipedInputStream pis; public Consumer1(PipedInputStream pis) { this.pis = pis; } public void run() { try { while(true) { System.out.println("consumer1:"+pis.read()); } } catch (IOException e) { e.printStackTrace(); } } } |
·?程序启动后,就可以看到producer线程往consumer1线程发送数据
consumer1:0
consumer1:1
consumer1:2
consumer1:3
......
·
管道流虽然使用起来方便,但是也有一些缺点
·
o
管道流只能在两个线程之间传递数据 。线程consumer1和consumer2同时从pis中read数据,当线程producer往管道流中写入一段数据后,每一个时刻只有一个线程能获取到数据,并不是两个线程都能获取到producer发送来的数据,因此一个管道流只能用于两个线程间的通讯。不仅仅是管道流,其他IO方式都是一对一传输。
o
o
管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流 。可以看到上面的例子中,线程producer通过管道流向线程consumer发送数据,如果线程consumer想给线程producer发送数据,则需要新建另一个管道流pos1和pis1,将pos1赋给consumer1,将pis1赋给producer,具体例子本文不再多说。
o
· 管道(Pipe) :管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
· 命名管道(named pipe) :命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
· 信号(Signal) :信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
· 消息(Message)队列 :消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
· 共享内存 :使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
· 内存映射(mapped memory) :内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
· 信号量(semaphore) :主要作为进程间以及同一进程不同线程之间的同步手段。
· 套接口(Socket) :更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:linux和System V的变种都支持套接字。
·??个人介绍
·??·??springMVC拦截器和过滤器的区别
http://blog.csdn.net/chenleixing/article/details/44573495
·??·??excel导出功能如何实现
http://blog.csdn.net/gaojinshan/article/details/30221729
·??·??SpringIOC和AOP,动态代理实现方式
·??·??自动装配有哪几种方式,自动装配有什么局限性
模式 | 说明 |
Default | 在每个bean中都一个autowire=default的默认配置它的含义是: 采用beans和跟标签中的default-autowire="属性值"一样的设置。 ? |
On | 不使用自动装配,必须通过ref元素指定依赖,默认设置。 ? |
ByNname | 根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。例如,在bean定义中将autowire设置为by?name,而该bean包含master属性(同时提供setMaster(..)方法),Spring就会查找名为master的bean定义,并用它来装配给master属性。 |
Bytype | 如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,那么将会抛出异常,并指出不能使用byType方式进行自动装配。若没有找到相匹配的bean,则什么事都不发生,属性也不会被设置。如果你不希望这样,那么可以通过设置dependency-check="objects"让Spring抛出异常。 ? |
Constructor | 与byType的方式类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。 |
Antodetect | 通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。如果发现默认的构造器,那么将使用byType方式。 ? |
注意:
byName?和byType
在使用的过程中必须保证bean能够初始化,否则的话会出现bug
如果有默认的无参数的构造器就不需要多余的配置
如果有带有参数的构造器,那在bean的配置中必须配置器初始化的参数?或者在bean中添加无参数的构造器
基本类型不可以装配,精准度?易混乱bean必须可以获得
·??·??数据库引擎,Innodb,MyISAM的区别
1.?MySQL默认采用的是MyISAM。
2.?MyISAM不支持事务,而InnoDB支持。InnoDB的AUTOCOMMIT默认是打开的,即每条SQL语句会默认被封装成一个事务,自动提交,这样会影响速度,所以最好是把多条SQL语句显示放在begin和commit之间,组成一个事务去提交。
3.?InnoDB支持数据行锁定,MyISAM不支持行锁定,只支持锁定整个表。即MyISAM同一个表上的读锁和写锁是互斥的,MyISAM并发读写时如果等待队列中既有读请求又有写请求,默认写请求的优先级高,即使读请求先到,所以MyISAM不适合于有大量查询和修改并存的情况,那样查询进程会长时间阻塞。因为MyISAM是锁表,所以某项读操作比较耗时会使其他写进程饿死。
4.?InnoDB支持外键,MyISAM不支持。
5.?InnoDB的主键范围更大,最大是MyISAM的2倍。
6.?InnoDB不支持全文索引,而MyISAM支持。全文索引是指对char、varchar和text中的每个词(停用词除外)建立倒排序索引。MyISAM的全文索引其实没啥用,因为它不支持中文分词,必须由使用者分词后加入空格再写到数据表里,而且少于4个汉字的词会和停用词一样被忽略掉。
7.?MyISAM支持GIS数据,InnoDB不支持。即MyISAM支持以下空间数据对象:Point,Line,Polygon,Surface等。
8.?没有where的count(*)使用MyISAM要比InnoDB快得多。因为MyISAM内置了一个计数器,count(*)时它直接从计数器中读,而InnoDB必须扫描全表。所以在InnoDB上执行count(*)时一般要伴随where,且where中要包含主键以外的索引列。为什么这里特别强调“主键以外”?因为InnoDB中primary index是和raw data存放在一起的,而secondary index则是单独存放,然后有个指针指向primary key。所以只是count(*)的话使用secondary index扫描更快,而primary key则主要在扫描索引同时要返回raw data时的作用较大。
·??·??索引实现方式,B+树有什么特点,B树和B+数的区别,B+树的实现方式
·??·??索引的最左配原则
·??·??HashMap和HashSet的区别,HashMap是有序的吗
·??·??LinkedHashMap,保证什么有序,底层实现。
·??·??java多线程的方式,FutureTask CallAble介绍,CallAble和Runnable的区别
http://www.importnew.com/17572.html
·??·??线程池
·??·??类的加载过程,双亲委派机制
上午交叉面,下午已回绝。四面挂,心痛。分享面经,为后面校招攒人品。
一面:(8月1号上午:电话面试:80分32秒)
1.自我介绍?
2.做过哪些项目?项目中遇到哪些难点,你是怎样解决的?单点登录系统说一下?分布式缓存的使用场景?(说好的基础呢,上来就是项目,毫无准备,导致好多东西都记不起来了。面试官还说“那你说一个你记得的项目”,手动无奈。。。)
分布式缓存的典型应用场景可分为以下几类:
1)页面缓存.用来缓存Web页面的内容片段,包括HTML、CSS和图片等,多应用于社交网站等;
2)应用对象缓存.缓存系统作为ORM框架的二级缓存对外提供服务,目的是减轻数据库的负载压力,加速应用访问;
3)状态缓存.缓存包括Session会话状态及应用横向扩展时的状态数据等,这类数据一般是难以恢复的,对可用性要求较高,多应用于高可用集群;
4)并行处理.通常涉及大量中间计算结果需要共享;
5)事件处理.分布式缓存提供了针对事件流的连续查询(continuous query)处理技术,满足实时性需求;
6)极限事务处理.分布式缓存为事务型应用提供高吞吐率、低延时的解决方案,支持高并发事务请求处理,多应用于铁路、金融服务和电信等领域.
3.你实习的时候JDK用的是那个版本,这个版本有什么新的特性?
http://zcc888.gitee.io/2018/03/20/java/#more
Jdk7的新特性:
1.对集合类的语言支持;(??)
2.自动资源管理;
3.改进的通用实例创建类型推断;(??)
4.数字字面量下划线支持;(√)
5.switch中使用string;(√)
JSR292 的实现增加了一个 InvokeDynamic 的字节码指令来支持动态类型语言,使得在把源代码编译成字节码时并不需要确定方法的签名,即方法参数的类型和返回类型。当运行时执行 InvokeDynamic 指令时,JVM 会通过新的动态链接机制 Method Handles,寻找到真实的方法。
为了防止自定义多线程ClassLoad产生的死锁问题,java.lang.ClassLoader类增加了以下API。
4.G1回收器和其他回收器有什么区别?
并发与并行,部分代?自己就可以,局部复制全局标记整理堆>4G会比较好。
Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。你可以使用-XX:+UseStringDeduplication这个JVM参数来试一下这个特性。
Java 8中最大的改变就是持久代的移除,它原本是用来给类元数据,驻留字符串,静态变量来分配空间的。这在以前都是需要开发人员来针对那些会加载大量类的应用来专门进行堆比例的优化及调整。许多年来都是如此,这也正是许多OutOfMemory异常的根源,因此由JVM来接管它真是再好不过了。即便如此,它本身并不会减少开发人员将应用解耦到不同的JVM中的可能性。
每个回收器都有许多不同的开关和选项来进行调优,这可能会增加吞吐量,也可能会减少,这取决于你的应用的具体的行为了。在下一篇文章中我们会深入讲解配置这些算法的关键策略。
5.垃圾回收为什么会停顿?哪些对象可能作为GCRoots?
虚拟机栈中引用的对象?方法区(静态属性,常量引用的对象)本地方法栈中引用的对象。