面试心得与总结-——答案整理_3 持续更新

21. 实现多线程的两种方法:Thread与Runable。
1:用Thread类或者Runnable接口创建的线程本质上没什么区别,因为Thread类也是实现了Runnable接口的。只是他们创建的方式不一样;
2:还有一个区别就是java中只能继承一类,而可以实现很多方法,因此我们实现创建一个线程的时候尽量用Runnable创建。
3:创建方法方式:Thread thread=new Thread(“线程1”),thread.start();MyTread thread=new MyTread(线程1);new Thread(thread).start();


22. 线程同步的方法:sychronized、lock、reentrantLock等。

1:synchronized是一个同步锁关键字;他修饰对象有如下几种:修饰一个代码块(作用于调用这个代码块的对象),修饰一个方法(作用于调用该方法的对象),修饰一个静态方法(作用于调用方法该类的所有对象),修饰一个类(主要体现在synchronized(类名.class))(作用于调用方法该类的所有对象)。
2:上面中1中所说的对象还是所有对象,则可以有这样一个假设:一个类中有一个用syschronized修饰的方法,然后创建一个对象,然后将该对象放入两个线程中去,在线程中调用同步方法,则会发生互斥同步的现象,如果是两个对象分别放入两个线程中,则这两个对象中该方法不会发生互斥同步的现象,各做个的。但是如果是对该类的所有对象。则不管是几个多上个对象放入该线程中,都会发生互斥同步的现象。
3:发生上面情况的主要原因:每个线程有自己的栈,栈中存放着对应方法的栈帧(局部变量,操作数栈,返回地址,动态链接信息等),当创建两个对象,而且分别付给了两个线程,则在在栈帧中的对该对象的引用指向了两个对象中的资源,不存在竞争的情况;但是两个线程中栈帧中的引用指向了同一个对象,则会对资源形成竞争的情况,因此会发生互斥。对于对静态方法(静态方法是要载入各个线程的栈中执行的,因此静态方法是线程安全的,也就是在各个线程的栈中执行该静态方法,当碰到synchronized关键字的时候就上锁执行,别的线程就不能执行,别的线程为什么不能执行呢,就是涉及到锁的问题了。上锁的目的是针对资源)用synchronized,此时锁定的资源肯定是静态资源,静态资源存放在方法区,方法区是贡献的资源,因此会出现共享资源发生互斥同步现象。
4:有一个问题就是当一个对象被synchronized(this)上锁以后,该对象的非synchronized方法等是可以被别的线程调用的。synchronized的参数代表被上锁的对象,当代码要执行monitorenter(synchronized编译后的指令),会预先检查被上锁的对象的锁是否被别的线程持有,所以同一个对象如果多个synchronized块只能被一个线程当前拥有同步块的线程随意执行,而别的线程是不能执行该被锁对象的任何synchronized块,但是可以执行其他非同步块方法块方法,因为执行其他的方法会不会扫面当前对象是否被上锁。
Lock锁:
1:Lock存存在的原因:如果用synchronized修饰一个资源,当一个线程占有该锁事件比较长(如读取io或者sleep()的时候),别的线程想要获取该资源,机会干巴巴的在哪儿等待,造成效率底下。因此Lock出现时必然的。(还有一种情况就是对同一个文件操作,两个线程同时读是不互斥的,而两个一个写,一个读,或者两个都写是要发生互斥的,如果用sychronized,则两个线程读的时候也会发生互斥);
2:Lock与Sychronized关键字的区别:1:synchronized关键字jvm内置的关键字,而Lock是一个java.util.concurrent.locks包下的一个接口。2:sychronized的锁不用手动释放,而Lock的锁需要手动释放。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3:  由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
4:Lock接口中的几个方法:lock()、tryLock()(尝试获取锁,获取则返回true,否则false)、tryLock(long time, TimeUnit unit)(在规定时间内去获取)和lockInterruptibly()(如果一个线程正处于等待状态,则调用该方法可以中断等待,但是如果一个现在正则运行,他是不能中断的,只能中断处于阻塞的线程)是用来获取锁的。unLock()方法是用来释放锁的;
5:Lock对象自己就是一个锁,相当与synchronized()括号中的对象。reentrantLock类:
1:ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
2:ReadWriteLock接口,就是用来获取读锁和写锁的,ReentrantReadWriteLock类实现了readwriteLock接口。private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();rwl.readLock().lock();尝试获取读锁,如果一个线程占用了读锁,别的线程来申请读锁的时候,还是会给该锁,如果领个线程中是rwl.writeLock().lock()则代笔是申请写锁,此时会失败,synchronized并不具备这样的功能。


23. 锁的等级:方法锁、对象锁、类锁。
1:可重入锁:只得是如果一个线程获取了一个对象锁以后,如果该线程获取该对象锁锁定的方法块,则会直接进入,而不用重新申请锁;Lock和syschronized都是可重入锁。(可重入就设计到进入一次,计数器+1,没出来一次,计数器-1)
2:可中断锁:如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。Lock就是可中断锁,而sysnchronized是不可中断的。
3:公平锁:公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。非公平锁则可能是随机的;有的线程永远也不能获得该锁。synchronized就是非公平锁,ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
4:读写锁:读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
----------------------------------------------------------------------------
5:方法锁:也就是对方法加锁,这个比较简单:注意锁的对象是this本身;
6:对象锁:也就是以一个对象做为锁,也就是synchronized()关键字中的对象;此时该锁只是针对一个对象的,因为对象在内存中可以有多个,也可以有一个,注意;
7:类锁:就是以一个静态变量(或者class对象(类名.class))作为锁对象,或者以sychronized关键字修饰一个静态方法;此时锁住对象是一个类,而不是一个对象。


24. 写出生产者消费者模式。
1:有两种写法,用synchronized关键字和wait()/notifyAll()实现,另一种是用ReentrantLock锁和Condition变量来实现
2:使用Conditon变量对时候,记住这两种中文含义(中文描述很重要):notFull.wait()等待没有满对条件(此时应该是满了),notEmpty.signalAll()满足了不为空的条件,通知等待不为空的线程


25. ThreadLocal的设计理念与作用。
1:ThreadLocal理解:TheadLocal的创建方式ThreadLocal<Integer> local = new ThreadLocal<Integer>();同时local.set(100);那么就相当于Integer local=100;不要把本地线程变量看出一个容器,因为你多次调用local.set()方法以后总是存放的是最新的值,所以他不是容器,如果要在线程中放另一变量,则重新new一个线程变量,同local.get()方法获得的就相当于获得local的值,ThreadLocal只是包装了一下而已。
2:设计理念:ThreadLocal中有一个静态内部类ThreadLocalMap(该map就理解为hashMap吧,只是改map中的enty实体继承了弱引用,也就是说,如果该Map中的对象太多的时候,即使我们没有主动放弃,可能垃圾回收器回收);同时在Thread类中有一ThreadLocalMap的组合,为ThreadLocal.ThreadLocalMap<ThreadLocal,Object> threadLocals;从这里可以看出,当在一个线程中调用local.set(100)方法的时候,则在该线程对象的ThreadLocalMap中存入<local,100>;当通过local.get()方法的时候,则先获取当前线程,通过当前线程获取ThreadLocalMap,然后,通过该local变量为key值去获取对应的value(100);
3:TheadLocal变量不是用来实现资源共享的,而是用来实现资源的在各个线程中的隔离。各个线程单独占有。虽然也可用局部变量,但是有些场景,如果说资源只有一个的时候,就得使用ThreadLcoal变量。还有要注意是对一个对象,如ThreadLocal<Person>这种状况,则map中存的是指向person的引用。要特别小心。


26. ThreadPool用法与优势。

1: Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行(设置多长时间后该线程执行;每隔几秒执行一次)。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行(保证只有一个线程执行任务,与newFixedThreadPool(1)等价)。
2: 线程池用法:Executors中有很多静态方法可以用来创建线程池;Executor是一个工厂方法类;里面可以创建很多线程;所有的ThreadPool都实现了Executor这个接口;因此只要返回Executor(该接口只有一个方法,那就是executor(Runnable thread))就可以了。ExecutorService cachedThreadPool = Executors.newCachedThreadPool();cachedThreadPool.execute(new Runnable()   public void run() {  System.out.println(index); }  也执行一次execute方法,就产生一个新的线程;
3:线程池的优势:因为创建一个线程和杀掉一个线程花费的资源是比较大的,因此采用线程池,则将线程先创建好放在一个队列中,当需要的时候就从线程池中获取一个线程来执行任务;如果用完,则将线程放入该队列中。避免了多次的创建和销毁线程。


27. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。

1:本例介绍一个特殊的队列:BlockingQueue,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒,同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间时才会被唤醒继续操作。
2:BlockingQueue有四个具体的实现类,
ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小。其所含的对象是以FIFO(先入先出)顺序排序的。
LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。其所含的对象是以FIFO顺序排序的。
PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。
SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。
3:semaphore:Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,acquire()获取一个许可,如果没有就等待,而release()释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。
4:countdownLatch:CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。(当每个线程调用countdown方法直到将countdownlatch方法创建时数减为0时,那么之前调用await()方法的线程才会继续执行。有一点注意,那就是只执行一次,不能到0以后重新执行)
5:CyclicBarrier:类有一个整数初始值,此值表示将在同一点同步的线程数量。当其中一个线程到达确定点,它会调用await() 方法来等待其他线程。当线程调用这个方法,CyclicBarrier阻塞线程进入休眠直到其他线程到达。当最后一个线程调用CyclicBarrier 类的await() 方法,它唤醒所有等待的线程并继续执行它们的任务。(当等待的线程数量达到CyclicBarrier线程指定的数量以后(调用await方法的线程数),才一起往下执行,否则大家都在等待,注意:如果达到指定的线程数量的时候:则可以重新计数,上面的过程可以循环)
6:CountDownLatch是减计数方式,计数==0时释放所有等待的线程;CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。


28. wait()和sleep()的区别。

1:wait()是Object类中,与notify和notifyAll搭配使用,sleep()属于Thread类中的;1.CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。

2.CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
2:wait()的时候放弃锁,而sleep的时候不放弃锁,但是在sleep的时候,可以调用interrupt()函数来中断该线程;
3:suspend将当前线程挂起,要等到线程调用resume()方法,该线程才能重新执行。


29. foreach与正常for循环效率对比。

1:foreach在使用的过程中,会创建额外的iterator方法,而每次调用哪个hasNext,和next()方法,增加了更多的操作,对于访问数组的话for的效率要高于foreach;而且在foreach中,还要检查mod_count,避免导致快速失败,所以效率上会降低,但是由此可以看出foreach是线程安全的,而for不是线程安全的
2:什么时候使用:在多线程中,则使用foreach,如果是在方法中读局部变量的操作,则使用for。


30. Java IO与NIO。
1:javaIO与javaNIO区别:IO是基于流的,而NIO是基于缓存的。JavaIO每次从流中读取一个或者多个字节,知道读取所有的字节,不能前后移动流中的数据。而javaIO通过通道读取到缓存区中,需要可在缓存区中前后移动,
2:javaIO是阻塞的,而NIO是非阻塞的;IO阻塞当一个线程调用read()方法或者write()方法的时候,该线程会阻塞直到数据读取或者写完毕,中间线程不能干别的事情;而在NIO中,以个线程可以通过通道去向缓存中发送一个读取数据请求,当没有数据时就会去做别的事,而不会一直等待直到读取到数据为止(io就是如果read了没有读取到想要的就会一直等待,直到读取到),NIO向文件缓存器写数据也一样,他可以只向其中写一部分,而不是全部写完(在写的时候线程当然不能做别的事情了)。
3:选择器:NIO允许一个线程来监视多个输入通道,可以注册一个选择器,然后用一个单独的线程来管理选择监控那一个通道;
4:运用场景:如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。同样,如果你需要维持许多打开的连接到其他计算机上,如P2P网络中,使用一个单独的线程来管理你所有出站连接,可能是一个优势。
5:NIO的都流程:先向通道发出读取数据请求,然后通道向buffer中写入数据,如果buffer中满或者不满,你都可以向其中读取数据。
6::同步异步:同步和异步是消息通信机制:同步:发出一个请求,然后一定要等待其返回,在返回期间,如果该线程去干别的事情,那就是不阻塞了(由于是同步,操作系统并不会通知你消息准备好了,所以你要过一会就去询问一次);组合起来就叫同步非阻塞;如果在等待期间,当前线程一直在等待,而不干别的事情就是同步阻塞;
7:异步:发送一个请求,我(应用程序)就不用去管了,等待操作系统来完成以后通知我,如果在发送了请求以后,在操作系统没有通知我这段时间,线程什么都不干(所以异步阻塞基本上不实用),一直在哪儿等着,那么就是异步阻塞,如果在操作系统告诉应用程序之前,线程去干别的,也就是异步非阻塞;
8:同步阻塞的BIO、同步非阻塞的NIO(要去轮询,单独效率低比BIO稍低,但是在实际运用中效率就高了)、异步非阻塞的AIO;
11:NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。(回答的核心,服务器中应用)
在NIO的处理方式中,当一个请求来的话,开启线程进行处理,可能会等待后端应用的资源(JDBC连接等),其实这个线程就被阻塞了,当并发上来的话,还是会有BIO一样的问题。
12:HTTP/1.1出现后,有了Http长连接,这样除了超时和指明特定关闭的http header外,这个链接是一直打开的状态的,这样在NIO处理中可以进一步的进化,在后端资源中可以实现资源池或者队列,当请求来的话,开启的线程把请求和请求数据传送给后端资源池或者队列里面就返回,并且在全局的地方保持住这个现场(哪个连接的哪个请求等),这样前面的线程还是可以去接受其他的请求,而后端的应用的处理只需要执行队列里面的就可以了,这样请求处理和后端应用是异步的.当后端处理完,到全局地方得到现场,产生响应,这个就实现了异步处理。(异步IO原理)

猜你喜欢

转载自fjding.iteye.com/blog/2319521